Skip to content
honteam  /   Heroes-of-Newerth...  /   Pull requests #18  /  
Open in github.dev Open in a new github.dev tab Open in codespace
Code

Schnarchnase advanced shopping lib #18

Open
wants to merge 19 commits into
base: Community
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
0 / 8 files viewed
751 changes: 12 additions & 739 deletions bots/behaviorLib.lua
Original file line number Original file line Diff line number Diff line change
@@ -15,6 +15,9 @@ local ceil, floor, pi, tan, atan, atan2, abs, cos, sin, acos, max, random
local nSqrtTwo = math.sqrt(2) local nSqrtTwo = math.sqrt(2)


behaviorLib.tBehaviors = {} behaviorLib.tBehaviors = {}

runfile "bots/shoppingLib.lua"

behaviorLib.nNextBehaviorTime = HoN.GetGameTime() behaviorLib.nNextBehaviorTime = HoN.GetGameTime()
behaviorLib.nBehaviorAssessInterval = 250 behaviorLib.nBehaviorAssessInterval = 250


@@ -884,9 +887,10 @@ HealAtWell - 0-100 based on HP% and proximity to the well
- HP%Fn = (30%, 20) (20%, 35) (10%, 60) exponentially increases the closer to 0% health - HP%Fn = (30%, 20) (20%, 35) (10%, 60) exponentially increases the closer to 0% health
- ProximityFn = 35 if < 600 away, otherwise a slow decay (601, 15) (2900, 10) - ProximityFn = 35 if < 600 away, otherwise a slow decay (601, 15) (2900, 10)
DontBreakChannel - 100 if channeling DontBreakChannel - 99 if channeling
Shop - 99 if just got into shop and not done buying PreGame - 97 if GetMatchTime() <= 0
PreGame - 98 if GetMatchTime() <= 0 Shop - 30,98 30 if there is something to buy, 98 if GetMatchTime() <= 0
StashSorting 30,100 30 if hero get access to stash, 100 if channeling (teleporting)
HitBulding - 36, 40 if {rax,throne} is not invulnerable, in range HitBulding - 36, 40 if {rax,throne} is not invulnerable, in range
TeamGroup - 35 if teambrain tells us TeamGroup - 35 if teambrain tells us
(HitBulding) - 23, 25 if {other,tower} is not invulnerable, in range, and wont aggro if tower (HitBulding) - 23, 25 if {other,tower} is not invulnerable, in range, and wont aggro if tower
@@ -958,15 +962,15 @@ end
---------------------------------- ----------------------------------
-- PreGame behavior -- PreGame behavior
-- --
-- Utility: 98 if MatchTime is <= 0 -- Utility: 97 if MatchTime is <= 0
-- Execute: Hold in the fountain -- Execute: Hold in the fountain
---------------------------------- ----------------------------------


function behaviorLib.PreGameUtility(botBrain) function behaviorLib.PreGameUtility(botBrain)
local utility = 0 local utility = 0


if HoN:GetMatchTime() <= 0 then if HoN:GetMatchTime() <= 0 then
utility = 98 utility = 97
end end


if botBrain.bDebugUtility == true and utility ~= 0 then if botBrain.bDebugUtility == true and utility ~= 0 then
@@ -2160,14 +2164,14 @@ tinsert(behaviorLib.tBehaviors, behaviorLib.TeamDefendBehavior)
---------------------------------- ----------------------------------
-- DontBreakChannel behavior -- DontBreakChannel behavior
-- --
-- Utility: 100 if you are channeling, 0 otherwise -- Utility: 99 if you are channeling, 0 otherwise
-- Execute: Do nothing -- Execute: Do nothing
---------------------------------- ----------------------------------
function behaviorLib.DontBreakChannelUtility(botBrain) function behaviorLib.DontBreakChannelUtility(botBrain)
local utility = 0 local utility = 0


if core.unitSelf:IsChanneling() then if core.unitSelf:IsChanneling() then
utility = 100 utility = 99
end end


if botBrain.bDebugUtility == true and utility ~= 0 then if botBrain.bDebugUtility == true and utility ~= 0 then
@@ -2909,736 +2913,5 @@ tinsert(behaviorLib.tBehaviors, behaviorLib.HealAtWellBehavior)
---------------------------------- ----------------------------------
-- Shop -- Shop
-- --
-- Utility: 99 if just entered well and not finished buying, 0 otherwise -- Please view ShoppingLib
---------------------------------- ----------------------------------

--TODO: separate "sort inventory and stash" into a second behavior so we can do so after using a TP in the well
--TODO: dynamic item builds
--TODO: dynamic regen purchases
--TODO: Courier use
--TODO: Use "CanAccessWellShop" instead of CanAccessStash

behaviorLib.nextBuyTime = HoN.GetGameTime()
behaviorLib.buyInterval = 1000
behaviorLib.finishedBuying = false
behaviorLib.canAccessShopLast = false

behaviorLib.printShopDebug = false

behaviorLib.BuyStateUnknown = 0
behaviorLib.BuyStateStartingItems = 1
behaviorLib.BuyStateLaneItems = 2
behaviorLib.BuyStateMidItems = 3
behaviorLib.BuyStateLateItems = 4
--[[ list code:
"# Item" is "get # of these"
"Item #" is "get this level of the item" --]]
behaviorLib.StartingItems = {"2 Item_DuckBoots", "2 Item_MinorTotem", "Item_HealthPotion", "Item_RunesOfTheBlight"}
behaviorLib.LaneItems = {"Item_Marchers", "2 Item_Soulscream", "Item_EnhancedMarchers"}
behaviorLib.MidItems = {"Item_Pierce 1", "Item_Immunity", "Item_Pierce 3"} --Pierce is Shieldbreaker, Immunity is Shrunken Head
behaviorLib.LateItems = {"Item_Weapon3", "Item_Sicarius", "Item_ManaBurn2", "Item_BehemothsHeart", "Item_Damage9" } --Weapon3 is Savage Mace. Item_Sicarius is Firebrand. ManaBurn2 is Geomenter's Bane. Item_Damage9 is Doombringer

behaviorLib.buyState = behaviorLib.BuyStateUnknown
behaviorLib.curItemList = {}
function behaviorLib.ProcessItemCode(itemCode)
local num = 1
local level = 1
local item = itemCode
local pos = strfind(itemCode, " ")
if pos then
local numTemp = strsub(itemCode, 1, pos - 1)
if tonumber(numTemp) ~= nil then
num = tonumber(numTemp)
item = strsub(itemCode, pos + 1)
end
end

pos = strfind(item, " ")
if pos then
local levelTemp = strsub(item, pos + 1)
if tonumber(levelTemp) ~= nil then
level = tonumber(levelTemp)
item = strsub(item, 1, pos - 1)
end
end

return item, num, level
end

function behaviorLib.DetermineBuyState(botBrain)
--This is for determining where in our buy pattern we are. We need this for when we dynamically reload the script.
local inventory = core.unitSelf:GetInventory(true)
local lists = {behaviorLib.LateItems, behaviorLib.MidItems, behaviorLib.LaneItems, behaviorLib.StartingItems}

--BotEcho('Checkin buy state')
for i, listItemStringTable in ipairs(lists) do
for j = #listItemStringTable, 1, -1 do
local listItemName = listItemStringTable[j]

local name, num, level = behaviorLib.ProcessItemCode(listItemName)
local tableItems = core.InventoryContains(inventory, name, true, true)
local numValid = #tableItems

if behaviorLib.printShopDebug then BotEcho("DetermineBuyState - Checking for "..num.."x "..name.." lvl "..level.." in Inventory") end

if tableItems then
for arrayPos, curItem in ipairs(tableItems) do
--BotEcho("DetermineBuyState - level of "..name.."... lvl "..curItem:GetLevel())
if curItem:GetLevel() < level or curItem:IsRecipe() then
tremove(tableItems, arrayPos)
numValid = numValid - 1
end
end
end

--if we have this, set the currentItem list to everything "past" this
if numValid >= num then
if j ~= #listItemStringTable then
if i == 1 then
behaviorLib.curItemList = core.CopyTable(behaviorLib.LateItems)
behaviorLib.buyState = behaviorLib.BuyStateLateItems
elseif i == 2 then
behaviorLib.curItemList = core.CopyTable(behaviorLib.MidItems)
behaviorLib.buyState = behaviorLib.BuyStateMidItems
elseif i == 3 then
behaviorLib.curItemList = core.CopyTable(behaviorLib.LaneItems)
behaviorLib.buyState = behaviorLib.BuyStateLaneItems
else
behaviorLib.curItemList = core.CopyTable(behaviorLib.StartingItems)
behaviorLib.buyState = behaviorLib.BuyStateStartingItems
end

--remove the items we already have

numToRemove = j - 1
for k = 0, numToRemove, 1 do
tremove(behaviorLib.curItemList, 1)
end
else
-- special case for last item in list
if i == 1 or i == 2 then
behaviorLib.curItemList = core.CopyTable(behaviorLib.LateItems)
behaviorLib.buyState = behaviorLib.BuyStateLateItems
elseif i == 3 then
behaviorLib.curItemList = core.CopyTable(behaviorLib.MidItems)
behaviorLib.buyState = behaviorLib.BuyStateMidItems
elseif i == 4 then
behaviorLib.curItemList = core.CopyTable(behaviorLib.LaneItems)
behaviorLib.buyState = behaviorLib.BuyStateLaneItems
end
end

--an item was found, we are all done here
if behaviorLib.printShopDebug then
BotEcho(" DetermineBuyState - Found Item!")
end

return
end
end
end

--we have found no items, start at the beginning
behaviorLib.curItemList = core.CopyTable(behaviorLib.StartingItems)
behaviorLib.buyState = behaviorLib.BuyStateStartingItems

if behaviorLib.printShopDebug then
BotEcho(" DetermineBuyState - No item found! Starting at the beginning of the buy list")
end
end

function behaviorLib.ShuffleCombine(botBrain, nextItemDef, unit)
local inventory = unit:GetInventory(true)

if behaviorLib.printShopDebug then
BotEcho("ShuffleCombine for "..nextItemDef:GetName())
end

--locate all my components
local componentDefs = nextItemDef:GetComponents()
local numComponents = #componentDefs
--printGetNameTable(componentDefs)
local slotsToMove = {}
if componentDefs and #componentDefs > 1 then
for slot = 1, 12, 1 do
local curItem = inventory[slot]
if curItem then
--if curItem IS the same type, check if it is our recipe and not another (completed) instance
local bRecipeCheck = curItem:GetTypeID() ~= nextItemDef:GetTypeID() or curItem:IsRecipe()

if behaviorLib.printShopDebug then
BotEcho(" Checking if "..tostring(slot)..", "..curItem:GetName().." is a component")
BotEcho(" NextItem Type check: "..tostring(curItem:GetTypeID()).." ~= "..tostring(nextItemDef:GetTypeID()).." is "..tostring(curItem:GetTypeID() ~= nextItemDef:GetTypeID()))
BotEcho(" IsRecipe chieck: "..tostring(curItem:IsRecipe()))
end

for compSlot, compDef in ipairs(componentDefs) do
if compDef then
if behaviorLib.printShopDebug then
BotEcho(" Component Type check: "..tostring(curItem:GetTypeID()).." == "..tostring(compDef:GetTypeID()).." is "..tostring(curItem:GetTypeID() == compDef:GetTypeID()))
end

if curItem:GetTypeID() == compDef:GetTypeID() and bRecipeCheck then
tinsert(slotsToMove, slot)
tremove(componentDefs, compSlot) --remove this out so we don't mark wrong duplicates

if behaviorLib.printShopDebug then
BotEcho(" Component found!")
end
break
end
end
end
elseif behaviorLib.printShopDebug then
BotEcho(" Checking if "..tostring(slot)..", EMPTY_SLOT is a component")
end
end

if behaviorLib.printShopDebug then
BotEcho("ShuffleCombine - numComponents "..numComponents.." #slotsToMove "..#slotsToMove)
BotEcho("slotsToMove:")
core.printTable(slotsToMove)
end

if numComponents == #slotsToMove then
if behaviorLib.printShopDebug then
BotEcho("Finding Slots to swap into")
end

--swap all components into your stash to combine them, avoiding any components in your stash already
local destSlot = 7
for _, slot in ipairs(slotsToMove) do
if slot < 7 then
--Make sure we don't swap with another component
local num = core.tableContains(slotsToMove, destSlot)
while num > 0 do
destSlot = destSlot + 1
num = core.tableContains(slotsToMove, destSlot)
end

if behaviorLib.printShopDebug then
BotEcho("Swapping: "..slot.." to "..destSlot)
end

unit:SwapItems(slot, destSlot)
destSlot = destSlot + 1
end
end
end
end
end

behaviorLib.BootsList = {"Item_PostHaste", "Item_EnhancedMarchers", "Item_PlatedGreaves", "Item_Steamboots", "Item_Striders", "Item_Marchers"}
behaviorLib.MagicDefList = {"Item_Immunity", "Item_BarrierIdol", "Item_MagicArmor2", "Item_MysticVestments"}
behaviorLib.sPortalKeyName = "Item_PortalKey"

function behaviorLib.SortInventoryAndStash(botBrain)
--[[
C) Swap items to fill inventory
1. Boots / +ms
2. Magic Armor
3. Homecoming Stone
4. PortalKey
5. Most Expensive Item(s) (price decending)
--]]
local unitSelf = core.unitSelf
local inventory = core.unitSelf:GetInventory(true)
local inventoryBefore = inventory
local slotsAvailable = {true, true, true, true, true, true} --represents slots 1-6 (backpack)
local slotsLeft = 6
local bFound = false

--TODO: optimize via 1 iteration and storing item refs in tables for each category, then filling 1-6
-- because this is hella bad and inefficent.

--boots
for slot = 1, 12, 1 do
local curItem = inventory[slot]

if behaviorLib.printShopDebug then
local name = "EMPTY_SLOT"
if curItem then
name = curItem:GetName()
end
BotEcho(" Checking if "..tostring(slot)..", "..name.." is a boot")
end

if curItem and (slot > 6 or slotsAvailable[slot] ~= false) then
for _, bootName in ipairs(behaviorLib.BootsList) do
if curItem:GetName() == bootName then

if behaviorLib.printShopDebug then
BotEcho(" Boots found")
end

for i = 1, #slotsAvailable, 1 do
if slotsAvailable[i] then
if behaviorLib.printShopDebug then BotEcho(" Swapping "..inventory[slot]:GetName().." into slot "..i) end

unitSelf:SwapItems(slot, i)
slotsAvailable[i] = false
slotsLeft = slotsLeft - 1
inventory[slot], inventory[i] = inventory[i], inventory[slot]
break
end
end
bFound = true
end

if bFound then
break
end
end
end

if bFound then
break
end
end

--magic armor
bFound = false
for slot = 1, 12, 1 do
local curItem = inventory[slot]
if slotsLeft < 1 then
break
end

if behaviorLib.printShopDebug then
local name = "EMPTY_SLOT"
if curItem then
name = curItem:GetName()
end
BotEcho(" Checking if "..tostring(slot)..", "..name.." has magic defense")
end

if curItem and (slot > 4 or slotsAvailable[slot] ~= false) then
for _, magicArmorItemName in ipairs(behaviorLib.MagicDefList) do
if curItem:GetName() == magicArmorItemName then
for i = 1, #slotsAvailable, 1 do
if slotsAvailable[i] then
unitSelf:SwapItems(slot, i)
slotsAvailable[i] = false
slotsLeft = slotsLeft - 1
inventory[slot], inventory[i] = inventory[i], inventory[slot]
break
end
end
bFound = true
end

if bFound then
break
end
end
end

if bFound then
break
end
end

--homecoming stone
bFound = false
local tpName = core.idefHomecomingStone:GetName()
for slot = 1, 12, 1 do
local curItem = inventory[slot]
if slotsLeft < 1 then
break
end

if behaviorLib.printShopDebug then
local name = "EMPTY_SLOT"
if curItem then
name = curItem:GetName()
end
BotEcho(" Checking if "..tostring(slot)..", "..name.." is a homecoming stone")
end

if curItem and (slot > 6 or slotsAvailable[slot] ~= false) then
if curItem:GetName() == tpName then
for i = 1, #slotsAvailable, 1 do
if slotsAvailable[i] then
unitSelf:SwapItems(slot, i)
slotsAvailable[i] = false
slotsLeft = slotsLeft - 1
inventory[slot], inventory[i] = inventory[i], inventory[slot]
break
end
end
bFound = true
end
end

if bFound then
break
end
end

--portal key
bFound = false
local sPortalKeyName = behaviorLib.sPortalKeyName
for slot = 1, 12, 1 do
local curItem = inventory[slot]
if slotsLeft < 1 then
break
end

if behaviorLib.printShopDebug then
local name = "EMPTY_SLOT"
if curItem then
name = curItem:GetName()
end
BotEcho(" Checking if "..tostring(slot)..", "..name.." is a homecoming stone")
end

if curItem and (slot > 6 or slotsAvailable[slot] ~= false) then
if curItem:GetName() == sPortalKeyName then
for i = 1, #slotsAvailable, 1 do
if slotsAvailable[i] then
unitSelf:SwapItems(slot, i)
slotsAvailable[i] = false
slotsLeft = slotsLeft - 1
inventory[slot], inventory[i] = inventory[i], inventory[slot]
break
end
end
bFound = true
end
end

if bFound then
break
end
end

if botBrain.printShopDebug then
BotEcho("Inv:")
printInventory(inventory)
end

--finally, most expensive
while slotsLeft > 0 do
--selection sort
local highestValue = 0
local highestSlot = -1
for slot = 1, 12, 1 do
local curItem = inventory[slot]
if curItem and (slot > 6 or slotsAvailable[slot] ~= false) then
local cost = 0
if not curItem:IsRecipe() then
cost = curItem:GetTotalCost()
end

if cost > highestValue then
highestValue = cost
highestSlot = slot
end
end
end

if highestSlot ~= -1 then

if botBrain.printShopDebug then
BotEcho("Highest Cost: "..highestValue.." slots available:")
core.printTable(slotsAvailable)
end

for i = 1, #slotsAvailable, 1 do
if slotsAvailable[i] then
if behaviorLib.printShopDebug then BotEcho(" Swapping "..inventory[highestSlot]:GetName().." into slot "..i) end

unitSelf:SwapItems(highestSlot, i)
slotsAvailable[i] = false
inventory[highestSlot], inventory[i] = inventory[i], inventory[highestSlot]
slotsLeft = slotsLeft - 1
break
end
end
else
--out of items
break
end
end

--compare backpack before and after to check for changes
local bChanged = false
for slot = 1, 6, 1 do
if inventory[slot] ~= inventoryBefore[slot] then
bChanged = true
break
end
end


return bChanged
end

function behaviorLib.SellLowestItems(botBrain, numToSell)
if numToSell > 12 then --sanity checking
return
end

local inventory = core.unitSelf:GetInventory(true)
local lowestValue
local lowestSlot

while numToSell > 0 do
lowestValue = 99999
for slot = 1, 12, 1 do
local curItem = inventory[slot]
if curItem then
local cost = curItem:GetTotalCost()

if cost < lowestValue then
lowestValue = cost
lowestItem = curItem
end
end
end

if lowestItem then
BotEcho("Selling "..lowestItem:GetName().." in slot "..lowestItem:GetSlot())
core.unitSelf:Sell(lowestItem)
inventory[lowestItem:GetSlot()] = ""
numToSell = numToSell - 1
else
--out of items
return
end
end
end

function behaviorLib.NumberSlotsOpen(inventory)
local numOpen = 0
--BotEcho("#inventory "..#inventory)
for slot = 1, 12, 1 do
curItem = inventory[slot]
--BotEcho("NumberSlotsOpen - Checking Slot "..slot)
if curItem == nil then
--BotEcho(" slot is open")
numOpen = numOpen + 1
end
end
return numOpen
end

function behaviorLib.DetermineNextItemDef(botBrain)
local inventory = core.unitSelf:GetInventory(true)

--check if our last suggested buy was purchased
local name, num, level = behaviorLib.ProcessItemCode(behaviorLib.curItemList[1])

--BotEcho('Checkin item list for "'..name..'"')
local tableItems = core.InventoryContains(inventory, name, true, true)

if behaviorLib.printShopDebug then
BotEcho("DetermineNextItemDef - behaviorLib.curItemList")
core.printTable(behaviorLib.curItemList)
BotEcho("DetermineNextItemDef - Checking for "..num.."x "..name.." lvl "..level.." in Inventory")
end

local idefCurrent = HoN.GetItemDefinition(name)
local bStackable = idefCurrent:GetRechargeable() --"rechargeable" items are items that stack

local numValid = 0
if not bStackable then
if tableItems then
numValid = #tableItems
for arrayPos, curItem in ipairs(tableItems) do
if curItem:GetLevel() < level or curItem:IsRecipe() then
tremove(tableItems, arrayPos)
numValid = numValid - 1
if behaviorLib.printShopDebug then BotEcho('One of the '..name..' is not valid level or is a recipe...') end
end
end
end
else
num = num * idefCurrent:GetInitialCharges()
for arrayPos, curItem in ipairs(tableItems) do
numValid = numValid + curItem:GetCharges()
end
end

--if we have this, remove it from our active list
if numValid >= num then
if behaviorLib.printShopDebug then BotEcho('Found it! Removing it from the list') end
if #behaviorLib.curItemList > 1 then
tremove(behaviorLib.curItemList, 1)
else
if behaviorLib.printShopDebug then BotEcho('End of this list, switching lists') end
-- special case for last item in list
if behaviorLib.buyState == behaviorLib.BuyStateStartingItems then
behaviorLib.curItemList = core.CopyTable(behaviorLib.LaneItems)
behaviorLib.buyState = behaviorLib.BuyStateLaneItems
elseif behaviorLib.buyState == behaviorLib.BuyStateLaneItems then
behaviorLib.curItemList = core.CopyTable(behaviorLib.MidItems)
behaviorLib.buyState = behaviorLib.BuyStateMidItems
elseif behaviorLib.buyState == behaviorLib.BuyStateMidItems then
behaviorLib.curItemList = core.CopyTable(behaviorLib.LateItems)
behaviorLib.buyState = behaviorLib.BuyStateLateItems
else
--keep repeating our last item
end
end
end

if behaviorLib.printShopDebug then
BotEcho("DetermineNextItemDef - behaviorLib.curItemList")
core.printTable(behaviorLib.curItemList)
end

local itemName = behaviorLib.ProcessItemCode(behaviorLib.curItemList[1])
local retItemDef = HoN.GetItemDefinition(itemName)

if behaviorLib.printShopDebug then
if behaviorLib.curItemList[1] then
BotEcho("DetermineNextItemDef - itemName: "..itemName)
else
BotEcho("DetermineNextItemDef - No item in list! Check your code!")
end
end

return retItemDef
end

-------- Behavior Fns --------
function behaviorLib.ShopUtility(botBrain)
--BotEcho('CanAccessStash: '..tostring(core.unitSelf:CanAccessStash()))
local bCanAccessShop = core.unitSelf:CanAccessStash()

--just got into shop access, try buying
if bCanAccessShop and not behaviorLib.canAccessShopLast then
--BotEcho("Open for shopping!")
behaviorLib.finishedBuying = false
end

behaviorLib.canAccessShopLast = bCanAccessShop

local utility = 0
if bCanAccessShop and not behaviorLib.finishedBuying then
if not core.teamBotBrain.bPurchasedThisFrame then
utility = 99
end
end

if botBrain.bDebugUtility == true and utility ~= 0 then
BotEcho(format(" ShopUtility: %g", utility))
end

return utility
end


function behaviorLib.ShopExecute(botBrain)
--[[
Current algorithm:
A) Buy items from the list
B) Swap items to complete recipes
C) Swap items to fill inventory, prioritizing...
1. Boots / +ms
2. Magic Armor
3. Homecoming Stone
4. Most Expensive Item(s) (price decending)
--]]
if object.bUseShop == false then
return
end

-- Space out your buys
if behaviorLib.nextBuyTime > HoN.GetGameTime() then
return
end

behaviorLib.nextBuyTime = HoN.GetGameTime() + behaviorLib.buyInterval

--Determine where in the pattern we are (mostly for reloads)
if behaviorLib.buyState == behaviorLib.BuyStateUnknown then
behaviorLib.DetermineBuyState(botBrain)
end

local unitSelf = core.unitSelf
local bChanged = false
local bShuffled = false
local bGoldReduced = false
local tInventory = core.unitSelf:GetInventory(true)
local nextItemDef = behaviorLib.DetermineNextItemDef(botBrain)
local bMyTeamHasHuman = core.MyTeamHasHuman()
local bBuyTPStone = (core.nDifficulty ~= core.nEASY_DIFFICULTY) or bMyTeamHasHuman

--For our first frame of this execute
if bBuyTPStone and core.GetLastBehaviorName(botBrain) ~= core.GetCurrentBehaviorName(botBrain) then
if nextItemDef:GetName() ~= core.idefHomecomingStone:GetName() then
--Seed a TP stone into the buy items after 1 min, Don't buy TP stones if we have Post Haste
local sName = "Item_HomecomingStone"
local nTime = HoN.GetMatchTime()
local tItemPostHaste = core.InventoryContains(tInventory, "Item_PostHaste", true)
if nTime > core.MinToMS(1) and #tItemPostHaste then
tinsert(behaviorLib.curItemList, 1, sName)
end

nextItemDef = behaviorLib.DetermineNextItemDef(botBrain)
end
end

if behaviorLib.printShopDebug then
BotEcho("============ BuyItems ============")
if nextItemDef then
BotEcho("BuyItems - nextItemDef: "..nextItemDef:GetName())
else
BotEcho("ERROR: BuyItems - Invalid ItemDefinition returned from DetermineNextItemDef")
end
end

if nextItemDef then
core.teamBotBrain.bPurchasedThisFrame = true

--open up slots if we don't have enough room in the stash + inventory
local componentDefs = unitSelf:GetItemComponentsRemaining(nextItemDef)
local slotsOpen = behaviorLib.NumberSlotsOpen(tInventory)

if behaviorLib.printShopDebug then
BotEcho("Component defs for "..nextItemDef:GetName()..":")
core.printGetNameTable(componentDefs)
BotEcho("Checking if we need to sell items...")
BotEcho(" #components: "..#componentDefs.." slotsOpen: "..slotsOpen)
end

if #componentDefs > slotsOpen + 1 then --1 for provisional slot
behaviorLib.SellLowestItems(botBrain, #componentDefs - slotsOpen - 1)
elseif #componentDefs == 0 then
behaviorLib.ShuffleCombine(botBrain, nextItemDef, unitSelf)
end

local nGoldAmtBefore = botBrain:GetGold()
unitSelf:PurchaseRemaining(nextItemDef)

local nGoldAmtAfter = botBrain:GetGold()
bGoldReduced = (nGoldAmtAfter < nGoldAmtBefore)
bChanged = bChanged or bGoldReduced

--Check to see if this purchased item has uncombined parts
componentDefs = unitSelf:GetItemComponentsRemaining(nextItemDef)
if #componentDefs == 0 then
behaviorLib.ShuffleCombine(botBrain, nextItemDef, unitSelf)
end
end

bShuffled = behaviorLib.SortInventoryAndStash(botBrain)
bChanged = bChanged or bShuffled

if not bChanged then
if behaviorLib.printShopDebug then
BotEcho("Finished Buying!")
end

behaviorLib.finishedBuying = true
end
end


behaviorLib.ShopBehavior = {}
behaviorLib.ShopBehavior["Utility"] = behaviorLib.ShopUtility
behaviorLib.ShopBehavior["Execute"] = behaviorLib.ShopExecute
behaviorLib.ShopBehavior["Name"] = "Shop"
tinsert(behaviorLib.tBehaviors, behaviorLib.ShopBehavior)
70 changes: 27 additions & 43 deletions bots/botbraincore.lua
Original file line number Original file line Diff line number Diff line change
@@ -851,56 +851,40 @@ function core.ValidateItem(item)
end end


function core.FindItems(botBrain) function core.FindItems(botBrain)
--seach for the key Items of ours that we want to track


core.ValidateItem(core.itemGhostMarchers) local itemHandler = object.itemHandler
core.ValidateItem(core.itemHatchet) if not itemHandler then return end
core.ValidateItem(core.itemRoT)


local unitSelf = core.unitSelf core.itemHatchet = itemHandler:GetItem("Item_LoggersHatchet")

if core.itemHatchet and not core.itemHatchet.creepDamageMul then
if (core.itemGhostMarchers and core.itemHatchet and core.itemRoT) then if core.unitSelf:GetAttackType() == "melee" then
return core.itemHatchet.creepDamageMul = 1.32
end else
core.itemHatchet.creepDamageMul = 1.12
end
end


local inventory = unitSelf:GetInventory(false) core.itemRoT = itemHandler:GetItem("Item_ManaRegen3") or itemHandler:GetItem("Item_LifeSteal5") or itemHandler:GetItem("Item_NomesWisdom")
for slot = 1, 6, 1 do if core.itemRoT and not core.itemRoT.nNextUpdateTime then
local curItem = inventory[slot] local modifierKey = core.itemRoT:GetActiveModifierKey()
if curItem then core.itemRoT.bHeroesOnly = (modifierKey == "ringoftheteacher_heroes" or modifierKey == "abyssalskull_heroes" or modifierKey == "nomeswisdom_heroes")
if core.itemGhostMarchers == nil and curItem:GetName() == "Item_EnhancedMarchers" then core.itemRoT.nNextUpdateTime = 0
core.itemGhostMarchers = core.WrapInTable(curItem) core.itemRoT.Update = function()
core.itemGhostMarchers.expireTime = 0 local nCurrentTime = HoN.GetGameTime()
core.itemGhostMarchers.duration = 6000 if nCurrentTime > core.itemRoT.nNextUpdateTime then
core.itemGhostMarchers.msMult = 0.12 local modifierKey = core.itemRoT:GetActiveModifierKey()
--Echo("Saving ghostmarchers") core.itemRoT.bHeroesOnly = (modifierKey == "ringoftheteacher_heroes" or modifierKey == "abyssalskull_heroes" or modifierKey == "nomeswisdom_heroes")
end core.itemRoT.nNextUpdateTime = nCurrentTime + 800

if core.itemHatchet == nil and curItem:GetName() == "Item_LoggersHatchet" then
core.itemHatchet = core.WrapInTable(curItem)
if unitSelf:GetAttackType() == "melee" then
core.itemHatchet.creepDamageMul = 1.32
else
core.itemHatchet.creepDamageMul = 1.12
end
--Echo("Saving hatchet")
end

if core.itemRoT == nil and curItem:GetName() == "Item_ManaRegen3" then
core.itemRoT = core.WrapInTable(curItem)
core.itemRoT.bHeroesOnly = (curItem:GetActiveModifierKey() == "ringoftheteacher_heroes")
core.itemRoT.nNextUpdateTime = 0
core.itemRoT.Update = function()
local nCurrentTime = HoN.GetGameTime()
if nCurrentTime > core.itemRoT.nNextUpdateTime then
core.itemRoT.bHeroesOnly = (core.itemRoT:GetActiveModifierKey() == "ringoftheteacher_heroes")
core.itemRoT.nNextUpdateTime = nCurrentTime + 800
end
end
end end
end end
end end


return core.itemGhostMarchers = itemHandler:GetItem("Item_EnhancedMarchers")
if core.itemGhostMarchers and not core.itemGhostMarchers.expireTime then
core.itemGhostMarchers.expireTime = 0
core.itemGhostMarchers.duration = 6000
core.itemGhostMarchers.msMult = 0.12
end
end end


function core.DecayBonus(botBrain) function core.DecayBonus(botBrain)
38 changes: 34 additions & 4 deletions bots/flint/flint_main.lua
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,10 @@
--FlintBot v1.0 --FlintBot v1.0

--[[
shoppingLib advanced implementations:
-custom options: no item reservations and no purchase of consumables (62-85) (disabled)
-findItems is not needed (376-403)
-itemHandler (Shroud) (line 419)
--]]


local _G = getfenv(0) local _G = getfenv(0)
local object = _G.object local object = _G.object
@@ -59,6 +64,31 @@ core.tLanePreferences = {Jungle = 0, Mid = 4, ShortSolo = 4, LongSolo = 2, Short


object.heroName = 'Hero_FlintBeastwood' object.heroName = 'Hero_FlintBeastwood'


--------------------------------------
-- ShoppingLib Implementation
--------------------------------------

--setup references
local itemHandler = object.itemHandler
local shoppingLib = object.shoppingLib

--Example of setup-options
--[[
--changes to default settings
local tSetupOptions = {
--no item reservations (do not care about items the team will get)
bReserveItems = false,
--will not buy any consumables (potions, homecoming stones)
tConsumableOptions = false
}
--setup Shopping-Behavior || custom call, because we want to change the default behavior
shoppingLib.Setup(tSetupOptions)
--]]

--support ReloadBots (while testing)
shoppingLib.bDevelopeItemBuildSaver = true

-------------------------------- --------------------------------
-- Skills -- Skills
-------------------------------- --------------------------------
@@ -348,7 +378,7 @@ end
object.harassExecuteOld = behaviorLib.HarassHeroBehavior["Execute"] object.harassExecuteOld = behaviorLib.HarassHeroBehavior["Execute"]
behaviorLib.HarassHeroBehavior["Execute"] = HarassHeroExecuteOverride behaviorLib.HarassHeroBehavior["Execute"] = HarassHeroExecuteOverride



--[[
---------------------------------- ----------------------------------
-- FindItems Override -- FindItems Override
---------------------------------- ----------------------------------
@@ -375,7 +405,7 @@ local function funcFindItemsOverride(botBrain)
end end
object.FindItemsOld = core.FindItems object.FindItemsOld = core.FindItems
core.FindItems = funcFindItemsOverride core.FindItems = funcFindItemsOverride

--]]


---------------------------------- ----------------------------------
-- RetreatFromThreat Override -- RetreatFromThreat Override
@@ -391,7 +421,7 @@ function funcRetreatFromThreatExecuteOverride(botBrain)
if bDebugEchos then BotEcho("Checkin Shroud") end if bDebugEchos then BotEcho("Checkin Shroud") end
if not bActionTaken then if not bActionTaken then
--Shroud use --Shroud use
local itemStealth = core.itemStealth local itemStealth = itemHandler:GetItem("Item_Stealth")
if itemStealth and itemStealth:CanActivate() then if itemStealth and itemStealth:CanActivate() then
if bDebugEchos then BotEcho("CanActivate! nRetreatUtil: "..behaviorLib.lastRetreatUtil.." thresh: "..object.nRetreatStealthThreshold) end if bDebugEchos then BotEcho("CanActivate! nRetreatUtil: "..behaviorLib.lastRetreatUtil.." thresh: "..object.nRetreatStealthThreshold) end
if behaviorLib.lastRetreatUtil >= object.nRetreatStealthThreshold then if behaviorLib.lastRetreatUtil >= object.nRetreatStealthThreshold then
293 changes: 157 additions & 136 deletions bots/gravekeeper/gravekeeper_main.lua
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,5 @@
--[[ --[[
Gravekeeper v1.0b by Schnarchnase Gravekeeper v1.1 by Schnarchnase
The skills: The skills:
@@ -36,7 +36,8 @@ credits:
-V1P3R` Engi Bot (Kill Messages) -V1P3R` Engi Bot (Kill Messages)
-using code paradoxon870 (Laning) -using code paradoxon870 (Laning)
v1.1: update shoppingLib
v1.0b: initial bot
--]] --]]
local _G = getfenv(0) local _G = getfenv(0)
local object = _G.object local object = _G.object
@@ -84,8 +85,14 @@ local ceil, floor, pi, tan, atan, atan2, abs, cos, sin, acos, max, random
local BotEcho, VerboseLog, BotLog = core.BotEcho, core.VerboseLog, core.BotLog local BotEcho, VerboseLog, BotLog = core.BotEcho, core.VerboseLog, core.BotLog
local Clamp = core.Clamp local Clamp = core.Clamp


--shoppingLib implementation
local itemHandler = object.itemHandler
local shoppingLib = object.shoppingLib

--support ReloadBots (while testing)
shoppingLib.bDevelopeItemBuildSaver = true


BotEcho('loading schnasegrave_main...') BotEcho('loading gravekeeper_main...')


-------------------------------- --------------------------------
-- Lanes -- Lanes
@@ -963,160 +970,174 @@ object.HealAtWellExecuteOld = behaviorLib.HealAtWellExecute
behaviorLib.HealAtWellBehavior["Execute"] = HealAtWellExecuteFnOverride behaviorLib.HealAtWellBehavior["Execute"] = HealAtWellExecuteFnOverride




--Function removes any item that is not valid
local function funcRemoveInvalidItems()
core.ValidateItem(core.itemPostHaste)
core.ValidateItem(core.itemTablet)
core.ValidateItem(core.itemPortalKey)
core.ValidateItem(core.itemHellFlower)
core.ValidateItem(core.itemSheepstick)
core.ValidateItem(core.itemFrostfieldPlate)
core.ValidateItem(core.itemSteamboots)
core.ValidateItem(core.itemSacStone)
core.ValidateItem(core.itemGhostMarchers)
end

---------------------------------- ----------------------------------
-- FindItems Override -- FindItems Override
---------------------------------- ----------------------------------
local function funcFindItemsOverride(botBrain) local function funcFindItemsOverride(botBrain)

object.FindItemsOld(botBrain)
--Alternate item wasn't checked, so you don't need to look for new items. --shoppingLib update - call item by name (itemHandler)
if core.bCheckForAlternateItems then return end core.itemPostHaste = itemHandler:GetItem("Item_PostHaste")

core.itemTablet = itemHandler:GetItem("Item_PushStaff")
funcRemoveInvalidItems() core.itemPortalKey = itemHandler:GetItem("Item_PortalKey")

core.itemFrostfieldPlate = itemHandler:GetItem("Item_FrostfieldPlate")
--We only need to know about our current inventory. Stash items are not important. core.itemSacStone = itemHandler:GetItem("Item_SacrificialStone")
local inventory = core.unitSelf:GetInventory(true) core.itemSheepstick = itemHandler:GetItem("Item_Morph")
for slot = 1, 6, 1 do core.itemHellFlower = itemHandler:GetItem("Item_Silence")
local curItem = inventory[slot]
if curItem then
if core.itemPostHaste == nil and curItem:GetName() == "Item_PostHaste" then
core.itemPostHaste = core.WrapInTable(curItem)
elseif core.itemTablet == nil and curItem:GetName() == "Item_PushStaff" then
core.itemTablet = core.WrapInTable(curItem)
elseif core.itemPortalKey == nil and curItem:GetName() == "Item_PortalKey" then
core.itemPortalKey = core.WrapInTable(curItem)
elseif core.itemFrostfieldPlate == nil and curItem:GetName() == "Item_FrostfieldPlate" then
core.itemFrostfieldPlate = core.WrapInTable(curItem)
elseif core.itemSheepstick == nil and curItem:GetName() == "Item_Morph" then
core.itemSheepstick = core.WrapInTable(curItem)
elseif core.itemHellFlower == nil and curItem:GetName() == "Item_Silence" then
core.itemHellFlower = core.WrapInTable(curItem)
elseif core.itemSteamboots == nil and curItem:GetName() == "Item_Steamboots" then
core.itemSteamboots = core.WrapInTable(curItem)
elseif core.itemSacStone == nil and curItem:GetName() == "Item_SacrificialStone" then
core.itemSacStone = core.WrapInTable(curItem)
elseif core.itemGhostMarchers == nil and curItem:GetName() == "Item_EnhancedMarchers" then
core.itemGhostMarchers = core.WrapInTable(curItem)
core.itemGhostMarchers.expireTime = 0
core.itemGhostMarchers.duration = 6000
core.itemGhostMarchers.msMult = 0.12
end
end

end
end end
object.FindItemsOld = core.FindItems object.FindItemsOld = core.FindItems
core.FindItems = funcFindItemsOverride core.FindItems = funcFindItemsOverride



--Check for alternate items before shopping
local function funcCheckforAlternateItemBuild(botbrain)

--no further check till next shopping round
core.bCheckForAlternateItems = false

local unitSelf = core.unitSelf

--initialize item choices
if unitSelf.getPK == nil then
--BotEcho("Initialize item choices")
unitSelf.getSteamboots = false
unitSelf.getPushStaff = false
unitSelf.getPK = false
end


local nGPM = botbrain:GetGPM()
local nXPM = unitSelf:GetXPM()
local nMatchTime = HoN.GetMatchTime()
local bBuyStateLow = behaviorLib.buyState < behaviorLib.BuyStateMidItems

--Bad early game: skip GhostMarchers and go for more defensive items
if bBuyStateLow and nXPM < 170 and nMatchTime > core.MinToMS(5) and not unitSelf.getSteamboots then
--BotEcho("My early Game sucked. I will go for a defensive Build.")
unitSelf.getSteamboots = true
behaviorLib.MidItems =
{"Item_Steamboots", "Item_MysticVestments", "Item_Scarab", "Item_SacrificialStone", "Item_Silence"}

--Boots finished
elseif core.itemGhostMarchers or core.itemSteamboots then

--Mid game: Bad farm, so go for a tablet
if unitSelf:GetLevel() > 10 and nGPM < 240 and not unitSelf.getPushStaff then
--BotEcho("Well, it's not going as expected. Let's try a Tablet!")
unitSelf.getPushStaff = true
tinsert(behaviorLib.curItemList, 1, "Item_PushStaff")

--Good farm and you finished your Boots. Now it is time to pick a portal key
elseif nGPM >= 300 and not unitSelf.getPK then
--BotEcho("The Game is going good. Soon I will kill them with a fresh PK!")
unitSelf.getPK = true
tinsert(behaviorLib.curItemList, 1, "Item_PortalKey")
end
end
end


---------------------------------- ----------------------------------
-- Gravekeeper Standard Item Build -- Gravekeeper Item Build
---------------------------------- ----------------------------------
--[[ list code: --[[ list code:
"# Item" is "get # of these" "# Item" is "get # of these"
"Item #" is "get this level of the item" --]] "Item #" is "get this level of the item" --]]


behaviorLib.StartingItems = --ItemBuild
{"Item_RunesOfTheBlight", "2 Item_MarkOfTheNovice", "2 Item_MinorTotem", "Item_HealthPotion"}
behaviorLib.LaneItems =
{"Item_Marchers","Item_GraveLocket"}
behaviorLib.MidItems =
{"Item_EnhancedMarchers", "Item_Silence"}
behaviorLib.LateItems =
{"Item_Morph", "Item_FrostfieldPlate", "Item_PostHaste", "Item_Freeze", "Item_Damage9"}


--1.Starting items
shoppingLib.tStartingItems = {"Item_RunesOfTheBlight", "2 Item_MarkOfTheNovice", "2 Item_MinorTotem", "Item_HealthPotion"}


--2.Lane items
shoppingLib.tLaneItems = {"Item_Marchers","Item_GraveLocket"}


--3.1 item route bad
--shoppingLib.tMidItems = {"Item_EnhancedMarchers", "Item_Silence"} --Ghost Marchers and Hellflower

--3.2 item route good
--shoppingLib.tMidItems = {"Item_Steamboots", "Item_MysticVestments", "Item_Scarab", "Item_SacrificialStone", "Item_Silence"}


--[[ --4 late game items
Shopping Override: shoppingLib.tLateItems = {"Item_Morph", "Item_FrostfieldPlate", "Item_PostHaste", "Item_Freeze", "Item_Damage9"}
At start of shopping check for alternate items
Usual Shopping
After finished check for new Items
--]]


core.bCheckForAlternateItems = true --situational tablet
local function funcShopExecuteOverride(botBrain) --poor farm (<240 gpm at lvl 11+ and boots finished)
--check item choices
if core.bCheckForAlternateItems then
--BotEcho("Checking Alternate Builds")
funcCheckforAlternateItemBuild(botBrain)
end


local bOldShopping = object.ShopExecuteOld (botBrain) --situational pk
--well farmed (>= 300 gpm after boots finished)


--update item links and reset the check --Gravekeeper Shopping function
if behaviorLib.finishedBuying then local function GravekeeperItemBuilder()
core.FindItems() --called everytime your bot runs out of items, should return false if you are done with shopping
core.bCheckForAlternateItems = true local debugInfo = false
--BotEcho("FindItems")
end if debugInfo then BotEcho("Checking itembuilder of Gravekeeper") end


return bOldShopping --variable for new items / keep shopping
local bNewItems = false

--get itembuild decision table
local tItemDecisions = shoppingLib.tItemDecisions
if debugInfo then BotEcho("Found ItemDecisions"..type(tItemDecisions)) end

--decision helper
local nGPM = object:GetGPM()

--early game (start items and lane items

--If tItemDecisions["bStartingItems"] is not set yet, choose start and lane items
if not tItemDecisions.bStartingItems then
--insert decisions into our itembuild-table
core.InsertToTable(shoppingLib.tItembuild, shoppingLib.tStartingItems)
core.InsertToTable(shoppingLib.tItembuild, shoppingLib.tLaneItems)


--we have implemented new items, so we can keep shopping
bNewItems = true

--remember our decision
tItemDecisions.bStartingItems = true

--If tItemDecisions["bItemBuildRoute"] is not set yet, choose boots and item route
elseif not tItemDecisions.bItemBuildRoute then

local sBootsChosen = nil
local tMidItems = nil

--decision helper
local nMatchTime = HoN.GetMatchTime()
local nXPM = core.unitSelf:GetXPM()

--check for agressive or passive route
if nXPM <= 175 and nMatchTime > core.MinToMS(5) then
--Bad early game: go for more defensive items
sBootsChosen = "Item_Steamboots"
tMidItems = {"Item_MysticVestments", "Item_Scarab", "Item_SacrificialStone", "Item_Silence"}
else
--go aggressive
sBootsChosen = "Item_EnhancedMarchers"
tMidItems = {"Item_Silence"}
end

--insert decisions into our itembuild-table: the boots
tinsert(shoppingLib.tItembuild, sBootsChosen)

--insert items into default itemlist (Mid and Late-Game items)
tItemDecisions.tItemList = {}
tItemDecisions.nItemListPosition = 1
core.InsertToTable(tItemDecisions.tItemList, tMidItems)
core.InsertToTable(tItemDecisions.tItemList, shoppingLib.tLateItems)

--we have implemented new items, so we can keep shopping
bNewItems = true

--remember our decision
tItemDecisions.bItemBuildRoute = true

--need Tablet?
elseif not tItemDecisions.bGetTablet and core.unitSelf:GetLevel() > 10 and nGPM <= 250 then
--Mid game: Bad farm, so go for a tablet

--insert decisions into our itembuild-table
tinsert(shoppingLib.tItembuild, "Item_PushStaff")

--we have implemented new items, so we can keep shopping
bNewItems = true

--remember our decision
tItemDecisions.bGetTablet = true

--need Portal Key?
elseif not tItemDecisions.bGetPK and nGPM >= 300 then
--Mid game: High farm, so go for pk

--insert decisions into our itembuild-table
tinsert(shoppingLib.tItembuild, "Item_PortalKey")

--we have implemented new items, so we can keep shopping
bNewItems = true
--remember our decision
tItemDecisions.bGetPK = true

--all other items
else

--put default items into the item build list (One after another)
local tItemList = tItemDecisions.tItemList
local nItemListPosition = tItemDecisions.nItemListPosition

local sItemCode = tItemList[nItemListPosition]
if sItemCode then
--got a new item code

--insert decisions into our itembuild-table
tinsert(shoppingLib.tItembuild, sItemCode)

--next item position
tItemDecisions.nItemListPosition = nItemListPosition + 1

--we have implemented new items, so we can keep shopping
bNewItems = true
end

end

if debugInfo then BotEcho("Reached end of itembuilder-function. Keep shopping? "..tostring(bNewItems)) end
return bNewItems
end end
object.ShopExecuteOld = behaviorLib.ShopExecute object.oldItembuilder = shoppingLib.CheckItemBuild
behaviorLib.ShopBehavior["Execute"] = funcShopExecuteOverride shoppingLib.CheckItemBuild = GravekeeperItemBuilder



--#################################################################### --####################################################################
--#################################################################### --####################################################################
@@ -1173,4 +1194,4 @@ end
object.funcGetDeathKeysOld = core.GetDeathKeys object.funcGetDeathKeysOld = core.GetDeathKeys
core.GetDeathKeys = GetDeathKeysOverride core.GetDeathKeys = GetDeathKeysOverride


BotEcho('finished loading schnasegrave_main') BotEcho('finished loading gravekeeper_main')
5 changes: 4 additions & 1 deletion bots/magmus/magmus_main.lua
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,5 @@
--MagmusBot v1.0 --MagmusBot v1.0

--shoppingLib changes: Enable ReloadBots compatibility (lines 50-51)


local _G = getfenv(0) local _G = getfenv(0)
local object = _G.object local object = _G.object
@@ -47,6 +47,9 @@ local ceil, floor, pi, tan, atan, atan2, abs, cos, sin, acos, max, random
local BotEcho, VerboseLog, BotLog = core.BotEcho, core.VerboseLog, core.BotLog local BotEcho, VerboseLog, BotLog = core.BotEcho, core.VerboseLog, core.BotLog
local Clamp = core.Clamp local Clamp = core.Clamp


--support ReloadBots (while testing)
object.shoppingLib.bDevelopeItemBuildSaver = true

BotEcho('loading magmus_main...') BotEcho('loading magmus_main...')


-------------------------------- --------------------------------
2,307 changes: 2,307 additions & 0 deletions bots/shoppingLib.lua
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,2307 @@
--[[
Name: Advanced Shopping Library
Current Version: 1.0
Creator: Schnarchnase
Overview:
This library will advance the bots shopping system.
Features:
ItemHandler:
-no need for find-items
-call items by name and unit
-easier inventory tests
shoppingLib:
-Bots can use the courier
-Bots can take care of the courier (upgrade it, rebuy if it dies)
-Bots can reserve items (don't go for team items, if the team has one already or another bot reserved it)
-Bots can buy consumables periodically or on demand
-dynamic item builds supported
-desired item slots supported (e.g.: if bot has boots, put them in slot 1)
Tip:
Take a look at the WitchSlayer-Bot to setting your shopping experience!
Usage:
Set references:
local itemHandler = object.itemHandler
local shoppingLib = object.shoppingLib
Use of ItemHandler:
itemHandler:GetItem(sItemName, unit)
itemHandler:GetItem("Item_Astrolabe")
itemHandler:GetItem("Item_LoggersHatchet", unitBooboo)
Set up your preferences:
default setup options:
tSetupOptions = {
bReserveItems = true, -- true or false
bWaitForLaneDecision = false,-- true or false
tConsumableOptions ={
Item_HomecomingStone = true,-- true or false
Item_HealthPotion = true,-- true or false
Item_RunesOfTheBlight = true,-- true or false
Item_ManaPotion = true -- true or false
}, --{}, true or false
bCourierCare = false -- true or false
}
you only need to insert differences to default (same object structure)
shoppingLib.setup (tSetupOptions)
Example:
This bot will support his team, so he should upgrade the courier,
buy wards, but he shouldn't buy any Mana Potions
--Implement changes to default settings
local tSetupOptions = {
bCourierCare = true, --upgrade courier
bWaitForLaneDecision = true, --wait for lane decision before shopping
tConsumableOptions = {Item_ManaPotion = false} --don't autobuy Mana Potions
}
--call setup function
shoppingLib.Setup(tSetupOptions)
----------------------------------------------------------------
ReloadBots compatibility: (Set to true while testing)
shoppingLib.bDevelopeItemBuildSaver = false
Set desired item slots:
shoppingLib.SetItemSlotNumber(sItemName, nSlotNumber)
shoppingLib.SetItemSlotNumber("Item_FlamingEye", 4)
Request Consumables:
shoppingLib.RequestConsumable (sItemName, nNumber)
shoppingLib.RequestConsumable ("Item_FlamingEye", 5)
Dynamic item builds (Take a look at WitchSlayer):
Override
shoppingLib.CheckItemBuild()
Manually upgrade courier:
shoppingLib.DoCourierUpgrade(courier, currentGold)
Misc.:
shoppingLib.nSellBonusValue = 2000 --Add to sell cost to prevent selling of desired items
shoppingLib.nMaxHealthTreshold = 1000 --max Health for buying potions
shoppingLib.nMaxManaTreshold = 350 -- max Mana for buying potions
--]]

local _G = getfenv(0)
local object = _G.object

-- Shopping and itemHandler Position
object.itemHandler = object.itemHandler or {}
object.itemHandler.tItems = object.itemHandler.tItems or {}
object.shoppingLib = object.shoppingLib or {}

local core, eventsLib, behaviorLib, metadata, itemHandler, shoppingLib = object.core, object.eventsLib, object.behaviorLib, object.metadata, object.itemHandler, object.shoppingLib

local print, ipairs, pairs, string, table, next, type, tinsert, tremove, tsort, format, tostring, tonumber, strfind, strsub
= _G.print, _G.ipairs, _G.pairs, _G.string, _G.table, _G.next, _G.type, _G.table.insert, _G.table.remove, _G.table.sort, _G.string.format, _G.tostring, _G.tonumber, _G.string.find, _G.string.sub
local ceil, floor, pi, tan, atan, atan2, abs, cos, sin, acos, max, random
= _G.math.ceil, _G.math.floor, _G.math.pi, _G.math.tan, _G.math.atan, _G.math.atan2, _G.math.abs, _G.math.cos, _G.math.sin, _G.math.acos, _G.math.max, _G.math.random

local BotEcho, VerboseLog, BotLog = core.BotEcho, core.VerboseLog, core.BotLog


--debugInfo
shoppingLib.bDebugInfoGeneralInformation = false
shoppingLib.bDebugInfoItemHandler = false
shoppingLib.bDebugInfoShoppingFunctions = false
shoppingLib.bDebugInfoShoppingBehavior = false
shoppingLib.bDebugInfoCourierRelated = false

--if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("") end

----------------------------------------------------
--important advanced Shopping variables
----------------------------------------------------

--Lists
--Itembuild: list, position and decision
shoppingLib.tItembuild = shoppingLib.tItembuild or {} --itemcodes
shoppingLib.nItembuildPosition = shoppingLib.nItembuildPosition or 1 --position
shoppingLib.tItemDecisions = shoppingLib.tItemDecisions or {} --table of custom reminders
--Shoppinglist
shoppingLib.tShoppingList = shoppingLib.tShoppingList or {}

--Courier
shoppingLib.bCourierMissionControl = false
shoppingLib.nNextFindCourierTime = HoN.GetGameTime()

--other variables
shoppingLib.nNextItemBuildCheck = 600*1000
shoppingLib.nCheckItemBuildInterval = 10*1000

shoppingLib.nNextBuyTime = HoN.GetGameTime()
shoppingLib.nBuyInterval = 250 -- One Shopping Round per Behavior utility call

--Shopping Utility Values
shoppingLib.nShoppingUtilityValue = 30
shoppingLib.nShoppingPreGameUtilityValue = 98

--item is not avaible for shopping, retry it at a later time (mainly puzzlebox)
shoppingLib.tDelayedItems = {}

--developement only - set this to true in your botfiles, while in pre submission phase
shoppingLib.bDevelopeItemBuildSaver = false

--names of some items
shoppingLib.sNameHomecomingStone = "Item_HomecomingStone"
shoppingLib.sNamePostHaste = "Item_PostHaste"
shoppingLib.sNameHealthPostion = "Item_HealthPotion"
shoppingLib.sNameBlightRunes = "Item_RunesOfTheBlight"
shoppingLib.sNameManaPotions = "Item_ManaPotion"

--purchasable consumables
shoppingLib.tConsumables = {
Item_HomecomingStone = true,
Item_HealthPotion = true,
Item_RunesOfTheBlight = true,
Item_ManaPotion = true,
Item_FlamingEye = true, --Ward of Sight
Item_ManaEye = true, --Ward of Revelation
Item_DustOfRevelation = true --Dust
}

--List of desired item slots
shoppingLib.tDesiredItemSlots = {
Item_PostHaste = 1,
Item_EnhancedMarchers = 1,
Item_PlatedGreaves = 1,
Item_Steamboots = 1,
Item_Striders = 1,
Item_Marchers = 1,
Item_Immunity = 2,
Item_BarrierIdol = 2,
Item_MagicArmor2 = 2,
Item_MysticVestments = 2,
Item_PortalKey = 3,
Item_GroundFamiliar = 6,
Item_HomecomingStone = 6
}

--items in their desired item slot receive a bonus value
shoppingLib.nSellBonusValue = 2000

--check function on a periodic basis
shoppingLib.nNextCourierControl = HoN.GetGameTime()
shoppingLib.nCourierControlIntervall = 250

--Courierstates: 0: Bugged; 1: Fill Courier; 2: Delivery; 3: Fill Stash
shoppingLib.nCourierState = 0

--used item slots by our bot
shoppingLib.tCourierSlots = {}

--courier delivery ring
shoppingLib.nCourierDeliveryDistanceSq = 500 * 500

--only cast delivery one time (prevents courier lagging)
shoppingLib.bDelivery = false

--Courier Bug Time-Outs
shoppingLib.nCourierDeliveryTimeOut = 1000
shoppingLib.nCourierPositionTimeOut = shoppingLib.nCourierDeliveryTimeOut + 500

--courier repair variables
shoppingLib.nCourierBuggedTimer = 0
shoppingLib.vecCourierLastPosition = nil

--stop shopping if we are done
shoppingLib.bDoShopping = true
--stop shopping if we experience issues like stash is full
shoppingLib.bPauseShopping = false

--table of requested items
shoppingLib.tRequestedItemsQueue = {}

--Regen Tresholds
shoppingLib.nMaxHealthTreshold = 1000
shoppingLib.nMaxManaTreshold = 350

-----------------------------------------------
-----------------------------------------------
-- WIP-Functions
-----------------------------------------------
-----------------------------------------------

--function SyncWithDatabse
--[[
description: Saves your itembuild progress to ensure compatibility with ReloadBots
--]]
local function SyncWithDatabse()

local sEntry = object.myName

--load values
if not shoppingLib.bDatabaseLoaded then
if shoppingLib.bDebugInfoGeneralInformation then BotEcho("Loading Database") end
shoppingLib.bDatabaseLoaded = true
local tResult = GetDBEntry(sEntry, sEntry, false, HoN.GetRemainingPreMatchTime() > 0)
if tResult then
if shoppingLib.bDebugInfoGeneralInformation then BotEcho("Found entries in database") end
local tValueTable = tResult.value
if tValueTable then
if shoppingLib.bDebugInfoGeneralInformation then BotEcho("Reloading bot decisions") end
--have entries -- unpack them
shoppingLib.tItembuild = tValueTable[1]
shoppingLib.nItembuildPosition = tValueTable[2]
shoppingLib.tItemDecisions = tValueTable[3]
shoppingLib.tDelayedItems = tValueTable[4]
shoppingLib.tCourierSlots = tValueTable[5]
itemHandler:UpdateDatabase()
end
end
end

--save values
local tTableToSave = {value = {}}
local tDataToSave = tTableToSave.value
tinsert (tDataToSave, shoppingLib.tItembuild)
tinsert (tDataToSave, shoppingLib.nItembuildPosition)
tinsert (tDataToSave, shoppingLib.tItemDecisions)
tinsert (tDataToSave, shoppingLib.tDelayedItems)
tinsert (tDataToSave, shoppingLib.tCourierSlots)

--GetDBEntry(sEntry, value, saveToDB, restoreDefault, setDefault)
GetDBEntry(sEntry, tTableToSave, true)
end

----------------------------------------------------
----------------------------------------------------
-- Item - Handler
----------------------------------------------------
----------------------------------------------------

--function GetItem
--[[
description: Returns the chosen item
parameters: sItemName : Name of the item (e.g. "Item_HomecomingStone");
unitSelected : in which inventory is the item? (if nil then core.unitSelf)
bIncludeStash: Check if your unit owns an item, don't try to use it with this option.
returns: the item or nil if not found
--]]
function itemHandler:GetItem(sItemName, unitSelected, bIncludeStash)

--no item name, no item
if not sItemName then
return
end
--default unit: hero-unit
if not unitSelected then
unitSelected = core.unitSelf
end

--get the item
local nUnitID = unitSelected:GetUniqueID()
local sItemKey = nUnitID..sItemName
local itemEntry = nUnitID and itemHandler.tItems[sItemKey]

--test if there is an item and if its still usable
if itemEntry and itemEntry:IsValid() then
--access = in the inventory of this unit
local bAccess = unitSelected:CanAccess(itemEntry.object)
--in stash, therefore not accessable
local nSlot = itemEntry:GetSlot()
local bInUnitsInventory = nSlot <= 6

if shoppingLib.bDebugInfoItemHandler then BotEcho("Access to item "..sItemName.." in slot "..tostring(nSlot).." granted: "..tostring(bAccess)) end

--don't delete if its acessable or in stash
if bInUnitsInventory and not bAccess then
--outdated entry
itemHandler.tItems[sItemKey] = nil
elseif bAccess or bIncludeStash then
if shoppingLib.bDebugInfoItemHandler then BotEcho("Return Item: "..sItemName) end

--return the item
return itemEntry
end
else
--item is not usable --> delete it
itemHandler.tItems[sItemKey] = nil
end
end

--function AddItem
--[[
description: Add an item to the itemHandler (Mainly used by next function UpdateDatabase)
parameters: itemCurrent : item to add;
unitSelected : in which inventory is the item? (if nil then core.unitSelf)
returns: true if the item was added
--]]
function itemHandler:AddItem(itemCurrent, unitSelected)

--no item, nothing to add
if not itemCurrent then
return
end

--default unit: hero-unit
if not unitSelected then
unitSelected = core.unitSelf
end

--itemName
local sItemName = itemCurrent:GetName()

--be sure that there is no item in database
if not itemHandler:GetItem(sItemName, unitSelected, true) then

local unitID = unitSelected:GetUniqueID()

if shoppingLib.bDebugInfoItemHandler then BotEcho("Add Item: "..sItemName) end

--add item
itemHandler.tItems[unitID..sItemName] = core.WrapInTable(itemCurrent)

--return success
return true
end

if shoppingLib.bDebugInfoItemHandler then BotEcho("Item already in itemHandler: "..sItemName) end
--return failure
return false
end

--function Update Database
--[[
description: Updates the itemHandler Database. Including all units with an inventory (courier, Booboo, 2nd Courier etc.)
parameters: bClear : Remove old entries?
--]]
function itemHandler:UpdateDatabase(bClear)

local unitSelf = core.unitSelf

--remove invalid entries
if bClear then
if shoppingLib.bDebugInfoItemHandler then BotEcho("Clear list") end
for slot, item in pairs(itemHandler.tItems) do
if item and not item:IsValid() then
--item is not valid remove it
itemHandler.tItems[slot] = nil
end
end
end

--hero Inventory
local inventory = unitSelf:GetInventory(true)

--insert all items of his inventory
for slot = 1, 12, 1 do
local itemCurrent = inventory[slot]
itemHandler:AddItem(itemCurrent, unitSelf)
end

--all other inventory units (Couriers, Booboo)
local tInventoryUnits = core.tControllableUnits and core.tControllableUnits["InventoryUnits"]
local unitCourier = shoppingLib.GetCourier()

if tInventoryUnits then
--do the same as above (insert all items)
for _, unit in ipairs(tInventoryUnits) do
if unit:IsValid() then
local unitInventory = unit:GetInventory()
--courier-unit has problems with multi-share
if unit == unitCourier then
local tCourierSlots = shoppingLib.tCourierSlots
for _, tCourierEntry in ipairs (tCourierSlots) do
local nSlot = tCourierEntry[1]
local itemCurrent = unitInventory[nSlot]
itemHandler:AddItem(itemCurrent, unit)
end
else
for nSlot = 1, 6, 1 do
local itemCurrent = unitInventory[nSlot]
itemHandler:AddItem(itemCurrent, unit)
end
end
end
end
end
end

-----------------
-- end of
-- Item Handler
-----------------

----------------------------------------------------
----------------------------------------------------
-- Shopping - Handler
----------------------------------------------------
----------------------------------------------------


--tChanges
--[[
(first option is default)
tChanges.Item_HomecomingStone: true or false
tChanges.Item_HealthPotion: true or false
tChanges.Item_RunesOfTheBlight: true or false
tChanges.Item_ManaPotion: true or false
--]]

--tSetupOptions
--[[
(first option is default)
tSetupOptions.bReserveItems: true or false
Check, if a team-item is already in the inventory of allies
tSetupOptions.bWaitForLaneDecision: false or true
Wait for the bots lane before start shopping (lane item builds)
tSetupOptions.tConsumableOptions: true, false/nil, tChanges
Shall the bot buy certain consumables periodically?
tSetupOptions.bCourierCare: false or true
Shall the bot upgrade and rebuy the courier?
--]]

--function Setup
--[[
description: Select the features of this file (can be called multiple times)
parameters: tSetupOptions: Table with option changes
--]]
function shoppingLib.Setup (tSetupOptions)

local tSetupOptions = tSetupOptions or {}

--initialize shopping
shoppingLib.bBuyItems = shoppingLib.bBuyItems or shoppingLib.bBuyItems == nil -- hold status or true
shoppingLib.bSetupDone = true

--Check, if item is already reserved by a bot or a player (basic) and update function for teambot
if tSetupOptions.bReserveItems ~= nil then
shoppingLib.bCheckItemReservation = tSetupOptions.bReserveItems
else
shoppingLib.bCheckItemReservation = shoppingLib.bCheckItemReservation or shoppingLib.bCheckItemReservation == nil --hold status or true
end

--Wait for lane decision before shopping?
if tSetupOptions.bWaitForLaneDecision ~= nil then
shoppingLib.bWaitForLaneDecision = tSetupOptions.bWaitForLaneDecision
else
shoppingLib.bWaitForLaneDecision = shoppingLib.bWaitForLaneDecision or false
end

--Consumables options
local tConsumableOptions = tSetupOptions.tConsumableOptions --can be a table or boolean
if tConsumableOptions == false then
shoppingLib.bBuyConsumables = false
else
shoppingLib.bBuyConsumables = true
shoppingLib.bBuyRegen = true
if type(tConsumableOptions) == "table" then
--found a table with changes, check each option
for sItemName, bValue in pairs (tConsumableOptions) do
if shoppingLib.tConsumables[sItemName] ~= nil then
shoppingLib.tConsumables[sItemName] = bValue
else
if shoppingLib.bDebugInfoGeneralInformation then BotEcho("Itemdefinition was not found in the default table: "..tostring(sItemName)) end
end
end
end
end

--Courier options
if tSetupOptions.bCourierCare ~= nil then
shoppingLib.bCourierCare = tSetupOptions.bCourierCare
else
shoppingLib.bCourierCare = shoppingLib.bCourierCare or false
end
end

--function ProcessItemCode
--[[
description: Decrypt the given itemcode-string
parameters: sItemCode: item Code String
returns: sItemName: item name of the given code
nNum: Amount of items
nLevel: Level of item
--]]
function shoppingLib.ProcessItemCode(sItemCode)
local nNum = 1
local nLevel = 1
local sItemName = sItemCode
local nPos = strfind(sItemCode, " ")
if nPos then
local sNumTemp = strsub(sItemCode, 1, nPos - 1)
if tonumber(sNumTemp) ~= nil then
nNum = tonumber(sNumTemp)
sItemName = strsub(sItemCode, nPos + 1)
end
end

nPos = strfind(sItemName, " ")
if nPos then
local sLevelTemp = strsub(sItemName, nPos + 1)
if tonumber(sLevelTemp) ~= nil then
nLevel = tonumber(sLevelTemp)
sItemName = strsub(sItemName, 1, nPos - 1)
end
end

return sItemName, nNum, nLevel
end

--function GetCourier
--[[
description: Returns the main courier
parameters: bForceUpdate: Force a courier-search operation
returns: the courier unit, if found
--]]
function shoppingLib.GetCourier(bForceUpdate)

--get saved courier
local unitCourier = shoppingLib.unitCourier
--if it is still alive return it
if unitCourier and unitCourier:IsValid() then
return unitCourier
end

--only search periodically
local nNow = HoN.GetGameTime()
if not bForceUpdate and shoppingLib.nNextFindCourierTime > nNow then
return
end

shoppingLib.nNextFindCourierTime = nNow + 1000

if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Courier was not found. Checking inventory units") end

--Search for a courier
local tInventoryUnits = core.tControllableUnits and core.tControllableUnits["InventoryUnits"] or {}
for key, unit in pairs(tInventoryUnits) do
if unit then
local sUnitName = unit:GetTypeName()
--Courier Check
if sUnitName == "Pet_GroundFamiliar" or sUnitName == "Pet_FlyngCourier" then
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Found Courier!") end

--my courier? share to team
if unit:GetOwnerPlayer() == core.unitSelf:GetOwnerPlayer() then
unit:TeamShare()
end

--set references and return the courier
shoppingLib.unitCourier = unit
return unit
end
end
end
end

--function CareAboutCourier
--[[
description: check if the courier, needs further attention
--]]
function shoppingLib.CareAboutCourier()

--Before the game starts, don't care about the courier
if HoN.GetMatchTime() <= 0 then return end

--get courier
local unitCourier = shoppingLib.GetCourier()

--do we have a courier
if unitCourier then
--got a ground courier? Send Upgrade-Order
if unitCourier:GetTypeName() == "Pet_GroundFamiliar" then
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Courier needs an upgrade - Will do it soon") end
shoppingLib.unitCourierDoUpgrade = true
end
else
--no courier - buy one
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("No Courier found, may buy a new one.") end
shoppingLib.bBuyNewCourier = true
end

end

--function DoCourierUpgrade(unitCourier, nGold)
--[[
description: Upgrade the courier, if the bot has enough gold
parameters: unitCourier: courier unit
nGold: current gold
returns the remaining gold
--]]
function shoppingLib.DoCourierUpgrade(unitCourier, nGold)

local unitCourier = unitCourier or shoppingLib.GetCourier()
local nMyGold = nGold or object:GetGold()

if unitCourier and nMyGold >= 200 then
shoppingLib.unitCourierDoUpgrade = false
if unitCourier:GetTypeName() == "Pet_GroundFamiliar" then
local abilCourierUpgrade = unitCourier:GetAbility(0)
core.OrderAbility(object, abilCourierUpgrade)
nMyGold = nMyGold - 200
end
end

return nMyGold
end

--function RequestConsumable
--[[
description: Request Consumables to be bought soon
parameters: sItemName: itemname, you want to purchase (regen, stones, wards and dust)
nAmount: number you want to purchase (doubles dust)
--]]
function shoppingLib.RequestConsumable (sItemName, nAmount)

--check if the requested item is a consumable
local sEntry = sItemName and shoppingLib.tConsumables[sItemName] ~= nil

if sEntry then
--item is a consumable
local nCount = nAmount or 1
for i = 1, nCount do
--purchase the requested number
tinsert(shoppingLib.tRequestedItemsQueue, sItemName)
end
end
end

--function Autobuy
--[[
description: Checks for automatic consumable purchases
returns: Keep buying items periodically
--]]
function shoppingLib.Autobuy()

local bKeepBuyingConsumables = false

local tConsumables = shoppingLib.tConsumables

--get info about ourself
local unitSelf = core.unitSelf
local unitCourier = shoppingLib.GetCourier()
local nMyGold = object:GetGold()

--read-Only ShoppingList
local tShoppingList = shoppingLib.tShoppingList

--Regen
if shoppingLib.bBuyRegen then

local bBuyRegen = false

--info about Health and Mana
local nMaxHealth = unitSelf:GetMaxHealth()
local nMaxMana = unitSelf:GetMaxMana()
local nHealthPercent = unitSelf:GetHealthPercent()
local nManaPercent = unitSelf:GetManaPercent()

--only buy Health-Regen as long we can use it
if nMaxHealth <= shoppingLib.nMaxHealthTreshold then
local sNameBlightRunes = shoppingLib.sNameBlightRunes
if tConsumables[sNameBlightRunes] then
bBuyRegen = true
--only buy Runes if we don't have some
local itemBlightRunes = itemHandler:GetItem(sNameBlightRunes, nil, true) or itemHandler:GetItem(sNameBlightRunes, unitCourier)
local itemDef = HoN.GetItemDefinition(sNameBlightRunes)
if not itemBlightRunes and core.tableContains (tShoppingList, itemDef) == 0 and nHealthPercent < 0.8 and nHealthPercent >= 0.6 then
local nCost = itemDef:GetCost()

--check if we can afford them
if nMyGold >= nCost then
shoppingLib.RequestConsumable (sNameBlightRunes, 1)
nMyGold = nMyGold - nCost
end
end
end
local sNameHealthPostion = shoppingLib.sNameHealthPostion
if tConsumables[sNameHealthPostion] then
bBuyRegen = true
--only buy potions if we don't have some
local itemHealthPotion = itemHandler:GetItem(sNameHealthPostion, nil, true) or itemHandler:GetItem(sNameHealthPostion, unitCourier)
local itemDef = HoN.GetItemDefinition(sNameHealthPostion)
if not itemHealthPotion and core.tableContains (tShoppingList, itemDef) == 0 and nHealthPercent < 0.6 and nManaPercent > 0.4 then
local nCost = itemDef:GetCost()

--check if we can afford them
if nMyGold >= nCost then
shoppingLib.RequestConsumable (sNameHealthPostion, 1)
nMyGold = nMyGold - nCost
end
end
end
end

--only buy Mana-Regen as long we can use it
if nMaxMana < shoppingLib.nMaxManaTreshold then
local sNameManaPotions = shoppingLib.sNameManaPotions
if tConsumables[sNameManaPotions] then
bBuyRegen = true
--only buy mana, if we don't have some
local itemManaPotion = itemHandler:GetItem(sNameManaPotions, nil, true) or itemHandler:GetItem(sNameManaPotions, unitCourier)
local itemDef = HoN.GetItemDefinition(sNameManaPotions)
if not itemManaPotion and core.tableContains (tShoppingList, itemDef) == 0 and nManaPercent < 0.4 and nHealthPercent > 0.5 then

local nCost = itemDef:GetCost()

if nMyGold >= nCost then
shoppingLib.RequestConsumable (sNameManaPotions, 1)
nMyGold = nMyGold - nCost
end
end
end
end

shoppingLib.bBuyRegen = bBuyRegen
bKeepBuyingConsumables = bBuyRegen
end


--homeomcing stones
local sNameHomecomingStone = shoppingLib.sNameHomecomingStone
if tConsumables[sNameHomecomingStone] then
--only buy stones if we have not Post Haste
local sNamePostHaste = shoppingLib.sNamePostHaste
local itemPostHaste = itemHandler:GetItem(sNamePostHaste, nil, true) or itemHandler:GetItem(sNamePostHaste, unitCourier)
if itemPostHaste then
tConsumables[sNameHomecomingStone] = false
shoppingLib.SetItemSlotNumber(sNameHomecomingStone)
else
bKeepBuyingConsumables = true
--only buy stones if we don't have some
local itemHomecomingStone = itemHandler:GetItem(sNameHomecomingStone, nil, true) or itemHandler:GetItem(sNameHomecomingStone, unitCourier)
local itemDef = HoN.GetItemDefinition(sNameHomecomingStone)
if not itemHomecomingStone and core.tableContains (tShoppingList, itemDef) == 0 then

local nCost = itemDef:GetCost()
local nAmount = 0

local nNow = HoN.GetMatchTime()

--10 min into the game buy them in pairs, if you can
if nMyGold >= 2*nCost and nNow > 600000 then
nAmount = 2
elseif nMyGold >= nCost and unitSelf:GetLevel() > 2 then
nAmount = 1
end

if nAmount > 0 then
shoppingLib.RequestConsumable (sNameHomecomingStone, nAmount)
nMyGold = nMyGold - nAmount * nCost
end
end
end
end

return bKeepBuyingConsumables
end

--function GetConsumables
--[[
description: Insert the requested items into the shopping list.
--]]
function shoppingLib.GetConsumables()

--automaticly purchase
if shoppingLib.bBuyConsumables and #shoppingLib.tRequestedItemsQueue == 0 then
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Checking for Consumables") end
shoppingLib.bBuyConsumables = shoppingLib.Autobuy()
end

--insert requested items into shopping list
local tRequestedItemsQueue = shoppingLib.tRequestedItemsQueue

local bCheckEntries = true
while bCheckEntries do
local sQueueEntry = tRequestedItemsQueue[1]
if sQueueEntry then
--found first entry, check definition
local itemDef = HoN.GetItemDefinition(sQueueEntry)
if itemDef then
--put item definition into shopping list
tinsert(shoppingLib.tShoppingList, 1, itemDef)
else
--could not get the item definition, skipping entry
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Item definition was not found: "..sQueueEntry) end
end
--remove entry
tremove(shoppingLib.tRequestedItemsQueue, 1)
else
--no entries left
bCheckEntries = false
end
end
end

--function CheckItemBuild -->>This file should be overriden<<--
--[[
description: Check your itembuild for some new items
returns: true if you should keep buying items
--]]
function shoppingLib.CheckItemBuild()

if shoppingLib.bDebugInfoGeneralInformation then BotEcho("You may want to override this function: shoppingLib.CheckItemBuild()") end

--just convert the standard lists into the new shopping list
if shoppingLib.tItembuild then
if #shoppingLib.tItembuild == 0 then

--compatibility for old bots
local tStartingItems = behaviorLib.StartingItems or shoppingLib.tStartingItems
local tLaneItems = behaviorLib.LaneItems or shoppingLib.tLaneItems
local tMidItems = behaviorLib.MidItems or shoppingLib.tMidItems
local tLateItems = behaviorLib.LateItems or shoppingLib.tLateItems

--insert into new table
core.InsertToTable(shoppingLib.tItembuild, tStartingItems)
core.InsertToTable(shoppingLib.tItembuild, tLaneItems)
core.InsertToTable(shoppingLib.tItembuild, tMidItems)
core.InsertToTable(shoppingLib.tItembuild, tLateItems)
else
--we reached the end of our itemlist. Done with shopping
return false
end
end
return true
end

--function GetAllComponents
--[[
description: Get all components of an item definition - including any sub-components
parameters: itemDef: the item definition
returns: a list of all components of an item (including sub-components)
--]]
function shoppingLib.GetAllComponents(itemDef)

--result table
local tResult = {}

if itemDef then
--info about this item definition
local bRecipe = not itemDef:GetAutoAssemble()
local tComponents = itemDef:GetComponents()

if tComponents then
local nNumberOfComponents = #tComponents
if nNumberOfComponents >1 then
--item is no basic omponent

if bRecipe then
--because we insert the recipe at the end we have to remove it in its componentlist
tremove(tComponents, nNumberOfComponents)
end

--get all sub-components of the components
for _, itemCompDef in ipairs (tComponents) do
local tComp = shoppingLib.GetAllComponents(itemCompDef)
--insert all sub-components in our list
for _, itemSubDef in ipairs (tComp) do
tinsert(tResult, itemSubDef)
end
end

--insert itemDef at the end of all other components
tinsert(tResult, itemDef)
else
--this item is a basis component
tinsert(tResult, itemDef)
end
else
BotEcho("Error: GetComponents returns no value. purchase may bug out")
end
else
BotEcho("Error: No itemDef found")
end

if shoppingLib.bDebugInfoShoppingFunctions then
BotEcho("Result info")
for nIndex,itemResultDef in ipairs (tResult) do
BotEcho("Position: "..tostring(nIndex).." ItemName: "..tostring((itemResultDef and itemResultDef:GetName()) or "Error val not found"))
end
BotEcho("End of Result Info")
end

return tResult
end

--function RemoveFirstByValue
--[[
description: Remove the first encounter of a specific value in a table
parameters: t: table to look at
valueToRemove: value which should be removed (first encounter)
returns: true if a value is successfully removed
--]]
function shoppingLib.RemoveFirstByValue(t, valueToRemove)

--no table, nothing to remove
if not t then
return false
end

local bSuccess = false
--loop through table
for i, value in ipairs(t) do
--found matching value
if value == valueToRemove then
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Removing itemdef "..tostring(value)) end
--remove entry
tremove(t, i)
bSuccess = true
break
end
end

return bSuccess
end

--function CheckItemsInventory
--[[
description: Check items in your inventory and removes any components you already own
parameters: tComponents: List of itemDef (usually result of shoppingLib.GetAllComponents)
returns: the remaining components to buy
--]]
function shoppingLib.CheckItemsInventory (tComponents)
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Checking Inventory Stuff") end

if not tComponents then
return
end

--result table
local tResult = core.CopyTable(tComponents)

--info about ourself
local unitSelf = core.unitSelf
local tInventory = unitSelf:GetInventory(true)

--courier items
local unitCourier = shoppingLib.GetCourier()
if unitCourier then
local tCourierInventory = unitCourier:GetInventory(false)
for _, tCourierEntry in ipairs (shoppingLib.tCourierSlots) do
local nSlot = tCourierEntry and tCourierEntry[1]
if nSlot then
tinsert(tInventory, tCourierInventory[nSlot])
end
end
end

--Get all components we own
if #tResult > 0 then

local tPartOfItem = {}

--Search inventory if we have any (sub-)components
for _, itemInventar in pairs(tInventory) do
if itemInventar then
local itemDef = itemInventar:GetItemDefinition()

--Search list for any matches
for _, itemCompDef in ipairs(tResult) do
if itemCompDef == itemDef then
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Found component. Name"..itemCompDef:GetName()) end

--found a component, add it to the list
tinsert(tPartOfItem, itemInventar)
break
end
end
end
end

--Delete (sub-)components of items we own
for _, item in ipairs (tPartOfItem) do
local itemDef = item:GetItemDefinition()
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Removing elements itemName"..itemDef:GetName()) end

--fount an item
if item then
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Found item") end

local bRecipe = item:IsRecipe()
local nLevel = not bRecipe and item:GetLevel() or 0

if bRecipe then
--item is a recipe remove the first encounter
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("this is a recipe") end
shoppingLib.RemoveFirstByValue(tResult, itemDef)
else
--item is no recipe, take further testing
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("not a recipe") end

--remove level-1 recipes
while nLevel > 1 do
shoppingLib.RemoveFirstByValue(tResult, itemDef)
nLevel = nLevel -1
end

--get sub-components
local tComponents = shoppingLib.GetAllComponents(itemDef)

--remove all sub-components and itself
for _,itemCompDef in pairs (tComponents) do
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Removing Component. "..itemCompDef:GetName()) end
shoppingLib.RemoveFirstByValue(tResult, itemCompDef)
end
end
end
end
end

return tResult
end

--function GetNextItem
--[[
description: Get the next item in the itembuild-list and put its components in the shopping-list
returns: true if you should keep shopping
--]]
local function GetNextItem()

local bKeepShopping = true

--references to our itembuild list
local tItemList = shoppingLib.tItembuild
local nListPos = shoppingLib.nItembuildPosition

--check if there are no more items to buy
if nListPos > #tItemList then
--get new items
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("shoppingLib.tItembuild: Index Out of Bounds. Check for new stuff") end
bKeepShopping = shoppingLib.CheckItemBuild()
end

--Get next item and put it into Shopping List
if bKeepShopping then
--go to next position
shoppingLib.nItembuildPosition = nListPos + 1

if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Next Listposition:"..tostring(nListPos)) end

--get item definition
local sNextItemCode = tItemList[nListPos]
local sName, nAmount, nLevel = shoppingLib.ProcessItemCode(sNextItemCode)

--care about ItemReservations?
if shoppingLib.bCheckItemReservation then
local teamBot = HoN.GetTeamBotBrain()
if teamBot and not teamBot.ReserveItem(sName) then
--item reservation failed,because it is already reserved
return GetNextItem()
end
end

local itemDef = HoN.GetItemDefinition(sName)

if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Name "..sName.." Anzahl "..nAmount.." Level"..nLevel) end


--get all components
local tItemComponents = shoppingLib.GetAllComponents(itemDef)

--Add Level Recipes
local nLevelRecipe = nLevel
while nLevelRecipe > 1 do
--BotEcho("Level Up")
tinsert (tItemComponents, itemDef)
nLevelRecipe = nLevelRecipe -1
end

--only do extra work if we need to
if nAmount > 1 then
--Add number of items
local tTemp = core.CopyTable(tItemComponents)
while nAmount > 1 do
--BotEcho("Anzahl +1")
core.InsertToTable(tTemp, tItemComponents)
nAmount = nAmount - 1
end

tItemComponents = core.CopyTable(tTemp)
end

--returns table of remaining components
local tReaminingItems = shoppingLib.CheckItemsInventory(tItemComponents)

--insert remaining items in shopping list
if #tReaminingItems > 0 then
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Remaining Components:") end
for _, itemCompDef in ipairs (tReaminingItems) do
if itemCompDef then
local sDefName = itemCompDef:GetName()
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Component "..sDefName) end
--only insert component if it not an autocombined element
if sDefName ~= sName or not itemCompDef:GetAutoAssemble() then
tinsert(shoppingLib.tShoppingList, itemCompDef)
end
end
end
else
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("No remaining components. Skip this item (If you want more items of this type increase number)") end
return GetNextItem()
end

end

if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("bKeepShopping? "..tostring(bKeepShopping)) end

return bKeepShopping
end

--function Print All
--[[
description: Print Your itembuild-list, your current itembuild-list position and your shopping-list
--]]
function shoppingLib.printAll()

--references to the lists
local tItembuild = shoppingLib.tItembuild
local tShoppingList = shoppingLib.tShoppingList
local nItembuildPosition = shoppingLib.nItembuildPosition

BotEcho("My itembuild:")
--go through whole list and print each component
for nSlot, item in ipairs(tItembuild) do
if item then
if nSlot == nItembuildPosition then BotEcho("Future items:") end
local sName = shoppingLib.ProcessItemCode(item) or "Error, no item name found!"
BotEcho("Slot "..tostring(nSlot).." Itemname "..sName)
end
end

BotEcho("My current shopping List")
--go through whole list and print each component
for _, itemCompDef in ipairs(tShoppingList) do
if itemCompDef then
BotEcho("Component Type check: "..tostring(itemCompDef:GetTypeID()).." is "..tostring(itemCompDef:GetName()))
else
BotEcho( "No desc")
end
end
end

--function UpdateItemList
--[[
description: Updates your itembuild-list and your shopping-list on a periodically basis. Update can be forced
parameters: bForceUpdate: Force a list update (usually called if your shopping-list is empty)
--]]
function shoppingLib.UpdateItemList(bForceUpdate)

--get current time
local nNow = HoN.GetGameTime()

--default setup if it is not overridden by the implementing bot
if not shoppingLib.bSetupDone then
shoppingLib.Setup()
end


--Check itembuild every now and then or force an update
if shoppingLib.nNextItemBuildCheck <= nNow or bForceUpdate then
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho(tostring(shoppingLib.nNextItemBuildCheck).." Now "..tostring(nNow).." Force Update? "..tostring(bForceUpdate)) end

if shoppingLib.bDevelopeItemBuildSaver and bForceUpdate then
SyncWithDatabse()
end

if shoppingLib.tShoppingList then
--Is your Shopping list empty? get new item-components to buy
if #shoppingLib.tShoppingList == 0 and shoppingLib.bBuyItems then
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Checking for next item") end
shoppingLib.bBuyItems = GetNextItem()
end

--check for consumables
if core.unitSelf:IsAlive() then
shoppingLib.GetConsumables()
end

--Are we in charge to buy and upgrade courier?
if shoppingLib.bCourierCare then
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Care about Courier") end
shoppingLib.CareAboutCourier()
end

--check for delayed items (Mainly puzzlebox re-purchase)
local tDelayedItems = shoppingLib.tDelayedItems
if #tDelayedItems > 0 then
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Found delayed items") end
local nSuccess = nil
--check if there are any items off cooldown
for i, tListEntry in ipairs(tDelayedItems) do
local nTime, itemDef = tListEntry[1], tListEntry[2]
if nTime <= nNow then
--try to re-purchase this item
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Insert Entry in shopping list") end
tinsert(shoppingLib.tShoppingList,1, itemDef)
nSuccess = i
break;
end
end
if nSuccess then
tremove (shoppingLib.tDelayedItems, nSuccess)
end
end
end

--reset cooldown
shoppingLib.nNextItemBuildCheck = nNow + shoppingLib.nCheckItemBuildInterval
if shoppingLib.bDebugInfoShoppingFunctions then shoppingLib.printAll() end
end
end


-----------------------------------------------
--Sort items
-----------------------------------------------

--function SetItemSlotNumber
--[[
description: Sets the slot for an itemName
parameters: sItemName: Name of the item
nSlot: Desired slot of this item (leave it to delete an entry)
returns: true if successfully set
--]]
function shoppingLib.SetItemSlotNumber(sItemName, nSlot)

if not sItemName then
return false
end

--insert slot number or delete entry if slot is nil
shoppingLib.tDesiredItemSlots[sItemName] = nSlot

return true
end

--function GetItemSlotNumber
--[[
description: Get the desired Slot of an item
parameters: sItemName: Name of the item
returns: the number of the desired Slot
--]]
function shoppingLib.GetItemSlotNumber(sItemName)
local tDesiredItemSlots = shoppingLib.tDesiredItemSlots
return tDesiredItemSlots and tDesiredItemSlots[sItemName] or 0
end


--function pair(a, b) --> helper-function for sorting tables
--[[
description: Checks if a table element is smaller than another one
parameters: a: first table element
b: second table element
returns: true if a is smaller than b
--]]
local function pair(a, b)
return a[1] < b[1]
end

--function SortItems
--[[
description: Sort the items in the units inventory
parameters: unitSelected: The unit which should sort its items
returns true if the inventory was changed
--]]
function shoppingLib.SortItems (unitSelected)
local bChanged = false

if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Sorting items probably") end

--default unit hero-unit
if not unitSelected then
unitSelected = core.unitSelf
end

--get inventory
local tInventory = unitSelected:GetInventory(true)

--item slot list
local tSlots = {false, false, false, false, false, false}

--list of cost and slot pairs
local tValueList = {}

--index all items
for nSlot, item in pairs (tInventory) do
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Current Slot"..tostring(nSlot)) end

--only add non recipe items
if not item:IsRecipe() then

--get item info
local sItemName = item:GetName()
local nItemTotalCost = item:GetTotalCost()
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Item "..sItemName) end

--get desiredSlot
local nDesiredSlot = shoppingLib.GetItemSlotNumber(sItemName)

if nDesiredSlot > 0 then
--go a recommended slot
--check for already existing entry
local nSavedItemSlot = tSlots[nDesiredSlot]
if nSavedItemSlot then
--got existing entry
--compare this old entry with the current one
local itemToCompare = tInventory[nSavedItemSlot]
local nItemCompareCost = itemToCompare:GetTotalCost()
if nItemTotalCost > nItemCompareCost then
--new item has a greater value, but old entry in value-list
tinsert(tValueList, {nItemTotalCost,nSavedItemSlot})
tSlots[nDesiredSlot] = nSlot
else
--old item is better, insert new item in value-list
tinsert(tValueList, {nItemTotalCost,nSlot})
end
else
--got a desiredSlot but don't have an item in it yet.
--Just put it in
tSlots[nDesiredSlot] = nSlot
end
else
--We don' have a recommended slot, just put it in the value list
--add itemcost and position to the value list
tinsert(tValueList, {nItemTotalCost,nSlot})
end
end
end

--sort our table (itemcost Down -> Top)
table.sort(tValueList, pair)

--insert missing entries with items from our value list (Top-down)
for nKey, nSlot in ipairs (tSlots) do
if not nSlot then
local tEntry = tValueList[#tValueList]
if tEntry then
tSlots[nKey] = tEntry[2]
tremove (tValueList)
end
end
end

--we have to take care for item swaps, because the slots will alter after a swap.
local nLengthSlots = #tSlots
for nKey, nSlot in ipairs (tSlots) do
--Replace any slot after this one
for nStart=nKey+1, nLengthSlots, 1 do
if tSlots[nStart] == nKey then
tSlots[nStart] = nSlot
end
end
end

--swap Items
for nSlot=1, 6, 1 do
local nThisItemSlot = tSlots[nSlot]
if nThisItemSlot then
--valid swap?
if nThisItemSlot ~= nSlot then
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Swapping Slot "..tostring(nThisItemSlot).." with "..tostring(nSlot)) end

--swap items
unitSelected:SwapItems(nThisItemSlot, nSlot)

bChanged = true
end
end
end

if shoppingLib.bDebugInfoShoppingFunctions then
BotEcho("Sorting Result: "..tostring(bChanged))
for nPosition, nFromSlot in pairs (tSlots) do
BotEcho("Item in Slot "..tostring(nPosition).." was swapped from "..tostring(nFromSlot))
end
end

return bChanged
end

--function SellItems
--[[
description: Sell a number of items from the unit's inventory (inc.stash)
parameters: nNumber: Number of items to sell;
unitSelected: Unit which should sell its items
tRestrictionSlotTable: table with accessable slots (courier related)
returns: true if the items were succcessfully sold
--]]
function shoppingLib.SellItems (nNumber, unitSelected, tRestrictionSlotTable)
local bChanged = false

local unitSelf = core.unitSelf

--default unit: hero-unit
if not unitSelected then
unitSelected = unitSelf
end

if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Sell items! Amount: "..tostring(nNumber).." Unit: "..tostring(unitSelected:GetTypeName())) end

--default number: 1; return if there is a negative value
if not nNumber then
nNumber = 1
elseif nNumber < 1 then
return bChanged
end

--get inventory
local tInventory = unitSelected:GetInventory()
local tStash = unitSelf:GetInventory(true)

--list of cost and slot pairs
local tValueList = {}

--Create an accesstable (you can not check item ownership)(12)
local tAccess = nil
--if we have restircted slots, only use them
if tRestrictionSlotTable then
tAccess = {false,false,false,false,false,false,true,true,true,true,true,true}
for _, tEntry in pairs(tRestrictionSlotTable) do
nSlot = tEntry[1] or tEntry -- SPecial case for courier-slots
tAccess[nSlot] = true
end
else
tAccess = {true,true,true,true,true,true,true,true,true,true,true,true}
end

--index items
for nSlot=1, 12, 1 do
if tAccess[nSlot] then
local tCurrentInventory = nSlot < 7 and tInventory or tStash
local itemCurrent = tCurrentInventory[nSlot]
if itemCurrent and not itemCurrent:IsRecipe() then
local nItemTotalCost = itemCurrent:GetTotalCost()
local sItemName = itemCurrent:GetName()
--give the important items a bonus in gold (Boots, Mystic Vestments etc.)
if unitSelf == unitSelected and nSlot == shoppingLib.GetItemSlotNumber(sItemName) then
nItemTotalCost = nItemTotalCost + shoppingLib.nSellBonusValue
end
--insert item in the list
tinsert(tValueList, {nItemTotalCost, nSlot})
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("Insert Slotnumber: "..tostring(nSlot).." Item "..sItemName.." Price "..tostring(nItemTotalCost)) end
end
end
end

--sort list (itemcost Down->Top)
table.sort(tValueList, pair)

local bStashOnly = true

--sell Items
while nNumber > 0 do
local tValueEntry = tValueList[1]
local nSellingSlot = tValueEntry and tValueEntry[2]
if nSellingSlot then
if shoppingLib.bDebugInfoShoppingFunctions then BotEcho("I am selling slotnumber "..tostring(nSellingSlot)) end

--Sell item by lowest TotalCost
if nSellingSlot <= 6 then
bStashOnly = false
unitSelected:SellBySlot(nSellingSlot)
else
unitSelf:SellBySlot(nSellingSlot)
end

bChanged = true

nNumber = nNumber -1

--remove from list
tremove (tValueList, 1)
else
--no item to sell
break
end
end

return bChanged, bStashOnly
end


--function NumberOfSlotsOpen
--[[
description: Counts the number of open slots
parameters: inventory: inventory of a unit
bStashOnly: Only count free stash slots
bHeroOnly: Only count free hero slots
returns: the number of free slots (all, stash or hero)
index of the first free Slot
--]]
function shoppingLib.NumberOfSlotsOpen(inventory, bStashOnly, bHeroOnly)
local nOpenSlots = 0
local nFirstFreeSlot = nil

local nStartIndex = (bStashOnly and 7) or 1
local nEndIndex = (not bStashOnly and bHeroOnly and 6) or 12
for nSlot = nStartIndex, nEndIndex, 1 do
curItem = inventory[nSlot]
if curItem == nil then
--no item is a free slot - count it
nOpenSlots = nOpenSlots + 1
if not nFirstFreeSlot then
nFirstFreeSlot = nSlot
end
end
end
return nOpenSlots, nFirstFreeSlot
end

-------------------
-- end of
-- Shopping Handler
-------------------

----------------------------------------------------
----------------------------------------------------
-- Shopping - Behavior
----------------------------------------------------
----------------------------------------------------
function shoppingLib.ShopUtility(botBrain)

local nUtility = 0

--don't shop till we know where to go
if shoppingLib.bWaitForLaneDecision then
if HoN.GetRemainingPreMatchTime() >= core.teamBotBrain.nInitialBotMove then
return nUtility
else
shoppingLib.bWaitForLaneDecision = false
end
end

local nShoppingUtilityValue = HoN.GetMatchTime() > 0 and shoppingLib.nShoppingUtilityValue or shoppingLib.nShoppingPreGameUtilityValue

local nMyGold = botBrain:GetGold()

local unitSelf = core.unitSelf
local bCanAccessStash = unitSelf:CanAccessStash()

--courier care
if shoppingLib.bCourierCare then
local unitCourier = shoppingLib.GetCourier()

--check we have to buy a new courier
if shoppingLib.bBuyNewCourier then
if unitCourier then
--there is a courier, no need to buy one
shoppingLib.bBuyNewCourier = false
shoppingLib.bPauseShopping = false
else
shoppingLib.bPauseShopping = true
if nMyGold >= 200 and bCanAccessStash then
--recheck courier to be safe
if not shoppingLib.GetCourier(true) then
--buy it
tinsert(shoppingLib.tShoppingList, 1, HoN.GetItemDefinition("Item_GroundFamiliar"))
end
shoppingLib.bBuyNewCourier = false
shoppingLib.bPauseShopping = false
end
end
end

--check if we have to upgrade courier
if shoppingLib.unitCourierDoUpgrade and nMyGold >= 200 then
nMyGold = shoppingLib.DoCourierUpgrade(unitCourier, nMyGold)
end
end

--still items to buy?
if shoppingLib.bDoShopping and not shoppingLib.bPauseShopping then

if not shoppingLib.bFinishedBuying then
nUtility = nShoppingUtilityValue
end

--if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("Check next item") end
local itemNextDef = shoppingLib.tShoppingList and shoppingLib.tShoppingList[1]

if not itemNextDef then
if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("No item definition in Shopping List. Start List update") end
shoppingLib.UpdateItemList(true)
itemNextDef = shoppingLib.tShoppingList[1]
end


if itemNextDef then

if nMyGold > itemNextDef:GetCost() then
if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("Enough gold to buy the item: "..itemNextDef:GetName()..". Current gold: "..tostring(nMyGold)) end
nUtility = nShoppingUtilityValue
shoppingLib.bFinishedBuying = false
if bCanAccessStash then
if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("Hero can access shop") end
nUtility = nShoppingUtilityValue * 3
end
end
else
BotEcho("Error no next item...Stopping any shopping")
shoppingLib.bDoShopping = false
end

end

return nUtility
end

function shoppingLib.ShopExecute(botBrain)

local nNow = HoN.GetGameTime()

--Space out your buys (one purchase per behavior-utility cycle)
if shoppingLib.nNextBuyTime > nNow then
return false
end

if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("Shopping Execute:") end

shoppingLib.nNextBuyTime = nNow + shoppingLib.nBuyInterval

local unitSelf = core.unitSelf

local bChanged = false
local tInventory = unitSelf:GetInventory(true)
local itemNextDef = shoppingLib.tShoppingList[1]

if itemNextDef then
if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("Found item. Buying "..itemNextDef:GetName()) end

local nGoldAmtBefore = botBrain:GetGold()
local nItemCost = itemNextDef:GetCost()

--enough gold to buy the item?
if nGoldAmtBefore >= nItemCost then

--check base shop access
local bCanAccessStash = unitSelf:CanAccessStash()

--check number of free slots
local nOpenSlotsAccessable, nExpectedSlot = shoppingLib.NumberOfSlotsOpen(tInventory, not bCanAccessStash)

--enough space?
if nOpenSlotsAccessable < 1 then

local bSuccess, bStashOnly = shoppingLib.SellItems (1)
--stop shopping, if we can't purchase items anymore, fix it with next stash access
shoppingLib.bPauseShopping = not bSuccess or not bStashOnly

else
unitSelf:PurchaseRemaining(itemNextDef)

local nGoldAmtAfter = botBrain:GetGold()
local bGoldReduced = (nGoldAmtAfter < nGoldAmtBefore)

--check purchase success
if bGoldReduced then
--gold was reduced, so we purchased something
local itemCurrent = tInventory[nExpectedSlot]
local itemDef = itemCurrent and itemCurrent:GetItemDefinition()
--purchase cases:
--1. item is not in its expected slot --> it has been combined elsewhere.
--2. item is in its expected slot and its definition matches --> sucessfully purchased
--3. another item is in its expected slot, but one of its components matches the purchased definition (Firebrand into Dawnbringer syndrom)
--4. another item is in its expected slot, but doesn't match --> different item was purchased (another component got lost for whatever reason)
if not itemCurrent or
itemDef == itemNextDef or
core.tableContains(shoppingLib.GetAllComponents(itemDef), itemNextDef) > 0 then
--purchase cases 1-3
if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("item Purchased. removing it from shopping list") end
tremove(shoppingLib.tShoppingList,1)
itemHandler:UpdateDatabase()
else
--purchase case 4
if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("Puchased something else") end
end

if shoppingLib.bDevelopeItemBuildSaver then SyncWithDatabse() end
else
local nMaxStock = itemNextDef:GetMaxStock()
if nMaxStock > 0 then
-- item may not be purchaseble, due to cooldown, so skip it
if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("Item not purchaseable due to cooldown. Item will be skipped") end
tremove(shoppingLib.tShoppingList,1)
--re-enter bigger items after cooldown delay; Current HoN: Only Puzzlebox
if nItemCost > 250 then
if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("Item is valuble, will try to repurchase it after some delay") end
local nItemRestockedTime = nNow + 120000
tinsert (shoppingLib.tDelayedItems, {nItemRestockedTime, itemNextDef})
end
else
if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("No Purchase of "..itemNextDef:GetName()..". Unknown exception waiting for stash access to fix it.") end
shoppingLib.bPauseShopping = true
end
end
bChanged = bChanged or bGoldReduced
end
end
end

--finished buying
if bChanged == false then
if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("Finished Buying!") end
shoppingLib.bFinishedBuying = true
shoppingLib.bStashFunctionActivation = true
local bCanAccessStash = unitSelf:CanAccessStash()
if not bCanAccessStash then
if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("CourierStart") end
shoppingLib.bCourierMissionControl = true
end
end
end
behaviorLib.ShopBehavior = {}
behaviorLib.ShopBehavior["Utility"] = shoppingLib.ShopUtility
behaviorLib.ShopBehavior["Execute"] = shoppingLib.ShopExecute
behaviorLib.ShopBehavior["Name"] = "Shop"
tinsert(behaviorLib.tBehaviors, behaviorLib.ShopBehavior)

----------------------------------------------------------
--Stash-Functions
--Sort your inventory, if in base
----------------------------------------------------------
function shoppingLib.StashUtility(botBrain)
local nUtility = 0

local unitSelf = core.unitSelf
local bCanAccessStash = unitSelf:CanAccessStash()

if bCanAccessStash then
--increase util when porting greatly
if core.unitSelf:IsChanneling() then
nUtility = 100
elseif shoppingLib.bStashFunctionActivation then
nUtility = HoN.GetMatchTime() > 0 and shoppingLib.nShoppingUtilityValue or shoppingLib.nShoppingPreGameUtilityValue
end
else
shoppingLib.bStashFunctionActivation = true
end

if shoppingLib.bDebugInfoShoppingBehavior and nUtility > 0 then BotEcho("Stash utility: "..tostring(nUtility)) end

return nUtility
end

function shoppingLib.StashExecute(botBrain)

local bSuccess = false

--no need to use stash?
if not shoppingLib.bStashFunctionActivation then
return bSuccess
end

local unitSelf = core.unitSelf
local bCanAccessStash = unitSelf:CanAccessStash()

if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("Can access stash "..tostring(bCanAccessStash)) end

--we can access the stash so just sort the items
if bCanAccessStash then
if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("Sorting items soon") end

bSuccess = shoppingLib.SortItems (unitSelf)

if shoppingLib.bDebugInfoShoppingBehavior then BotEcho("Sorted items"..tostring(bSuccess)) end

--don't sort items again in this stash-meeting (besides if we are using a tp)
shoppingLib.bStashFunctionActivation = false

--all Stashproblems should be fixed now
shoppingLib.bPauseShopping = false

itemHandler:UpdateDatabase(bSuccess)
end

--if we have a courier in inventory, activate him
local itemCourier = itemHandler:GetItem("Item_GroundFamiliar")
if itemCourier then
core.OrderItemClamp(botBrain, unitSelf, itemCourier)

--now we should have a new free slot, so we can resort the stash
shoppingLib.bStashFunctionActivation = true
end

return bSuccess
end

behaviorLib.StashBehavior = {}
behaviorLib.StashBehavior["Utility"] = shoppingLib.StashUtility
behaviorLib.StashBehavior["Execute"] = shoppingLib.StashExecute
behaviorLib.StashBehavior["Name"] = "Stash"
tinsert(behaviorLib.tBehaviors, behaviorLib.StashBehavior)


---------------------------------------------------
--Further Courier Functions
---------------------------------------------------

--fill courier with stash items and remeber the transfered slot
function shoppingLib.FillCourier(unitCourier)
local bSuccess = false

if not unitCourier then return bSuccess end

if shoppingLib.bDebugInfoCourierRelated then BotEcho("Fill COurier") end

--get info about inventory
local tInventory = unitCourier:GetInventory()
local tStash = core.unitSelf:GetInventory (true)

local tSlotsOpen = {}

--check open courier slots
for nSlot = 1, 6, 1 do
local item = tInventory[slot]
if not item then
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Slot "..tostring(nSlot).." is free") end
tinsert(tSlotsOpen, nSlot)
end
end
--transfer items to courier
local nOpenSlot = 1
for nSlot=12, 7, -1 do
local itemCurrent = tStash[nSlot]
local nFreeSlot = tSlotsOpen[nOpenSlot]
if itemCurrent and nFreeSlot then
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Swap "..tostring(nSlot).." with "..tostring(nFreeSlot)) end
unitCourier:SwapItems(nSlot, nFreeSlot)
local itemDef = itemCurrent:GetItemDefinition()
local tCourierEntry = {nFreeSlot, itemDef}
tinsert(shoppingLib.tCourierSlots, tCourierEntry)
nOpenSlot = nOpenSlot + 1
bSuccess = true
end
end

if bSuccess then
itemHandler:UpdateDatabase()
end

return bSuccess
end

--fill stash with the items from courier
function shoppingLib.FillStash(unitCourier)
local bSuccess = false

if not unitCourier then
return bSuccess
end

--get inventory information
local tInventory = unitCourier:GetInventory()
local tStash = core.unitSelf:GetInventory (true)

--any items to return to stash?
local tCourierSlots = shoppingLib.tCourierSlots
if not tCourierSlots then
return true
end

if shoppingLib.bDebugInfoCourierRelated then BotEcho("Fill Stash") end

-- return items to stash
local nLastItemSlot = #tCourierSlots
for nSlot=7, 12, 1 do
local itemInStashSlot = tStash[nSlot]
local tCourierEntry = tCourierSlots[nLastItemSlot]
local nItemSlot = tCourierEntry and tCourierEntry[1]
local itemInSlot = nItemSlot and tInventory[nItemSlot]
if not itemInSlot then
if shoppingLib.bDebugInfoCourierRelated then BotEcho("No item in Slot "..tostring(nItemSlot)) end
tremove(shoppingLib.tCourierSlots)
nLastItemSlot = nLastItemSlot - 1
elseif not itemInStashSlot then
local bIsTrackedItem = tCourierEntry[2] == itemInSlot:GetItemDefinition()
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Was this item tracked? Name: "..tostring(itemInSlot:GetName()).." Tracked: "..tostring(bIsTrackedItem)) end
if bIsTrackedItem then
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Swap "..tostring(nItemSlot).." with "..tostring(nSlot)) end
unitCourier:SwapItems(nItemSlot, nSlot)
end
tremove(shoppingLib.tCourierSlots)
nLastItemSlot = nLastItemSlot - 1
end
end

local nCourierSlotsUsed = #shoppingLib.tCourierSlots
if nCourierSlotsUsed == 0 then
bSuccess = true
else
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Still items remaining. Selling number of items: "..tostring(nCourierSlotsUsed)) end
shoppingLib.SellItems (nCourierSlotsUsed, unitCourier, tCourierSlots)
end

return bSuccess
end

--courier control function
local function CourierMission(botBrain, unitCourier)

local nCourierState = shoppingLib.nCourierState
local bOnMission = true

--check current state; 0: setting up courier (after reload); 1: fill courier; 2 deliver; 3 home
if nCourierState < 2 then
if nCourierState < 1 then
--nCourierState = 0 --> Setting up courier
if #shoppingLib.tCourierSlots > 0 then
--have sth. to deliver
shoppingLib.nCourierState = 2 -- Delivery
else
--fill courier
shoppingLib.nCourierState = 1 -- Filling
end
--Setting up complete
else
--nCourierState = 1 --> Filling courier phase
if unitCourier:CanAccessStash() then
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Stash Access") end
--fill courier
local bSuccess = shoppingLib.FillCourier(unitCourier)
if bSuccess then
--Item transfer successfull. Switch to delivery phase
shoppingLib.nCourierState = 2 -- Delivery
else
--no items transfered (no space or no items)
if nCourierState == 1.9 then -- 3-strike system
--3rd transfer attempt didn't solve the issue, stopping mission
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Something destroyed courier usage. Courier-Inventory is full or unit has no stash items") end
bOnMission = false
else
--waiting some time before trying again
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Can not transfer any items. Taking a time-out") end
local nNow = HoN.GetGameTime()
shoppingLib.nNextCourierControl = nNow + 5000
shoppingLib.nCourierState = shoppingLib.nCourierState +0.3
end
end
end
--Filling courier complete
end
else
if nCourierState < 3 then
--nCourierState = 2 --> Delivery

--unit is dead? abort delivery
if not core.unitSelf:IsAlive() then
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Hero is dead - returning Home") end
--abort mission and fill stash
shoppingLib.nCourierState = 3 -- Home

--home
local abilCourierHome = unitCourier:GetAbility(3)
if abilCourierHome then
core.OrderAbility(botBrain, abilCourierHome, nil, true)
return bOnMission
end
end

-- only cast delivery ability once (else it will lag the courier movement
if not shoppingLib.bDelivery then
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Courier uses Delivery!") end
--deliver
local abilCourierSend = unitCourier:GetAbility(2)
if abilCourierSend then
core.OrderAbility(botBrain, abilCourierSend, nil, true)
shoppingLib.bDelivery = true
return bOnMission
end
end

--activate speedburst
local abilCourierSpeed = unitCourier:GetAbility(1) and unitCourier:GetAbility(0)
if abilCourierSpeed and abilCourierSpeed:CanActivate() then
core.OrderAbility(botBrain, abilCourierSpeed)
return bOnMission
end

--check if courier is near hero to queue home-skill
local nDistanceCourierToHeroSq = Vector3.Distance2DSq(unitCourier:GetPosition(), core.unitSelf:GetPosition())
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Distance between courier and hero"..tostring(nDistanceCourierToHeroSq)) end

if nDistanceCourierToHeroSq <= shoppingLib.nCourierDeliveryDistanceSq then
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Courier is in inner circle") end

if not shoppingLib.bUpdateDatabaseAfterDelivery then
shoppingLib.bUpdateDatabaseAfterDelivery = true

if shoppingLib.bDebugInfoCourierRelated then BotEcho("Activate Home Skill !") end
--home
local abilCourierHome = unitCourier:GetAbility(3)
if abilCourierHome then
core.OrderAbility(botBrain, abilCourierHome, nil, true)
return bOnMission
end
end
else
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Courier is out of range") end
if shoppingLib.bUpdateDatabaseAfterDelivery then

if shoppingLib.bDebugInfoCourierRelated then BotEcho("Delivery done") end
shoppingLib.bUpdateDatabaseAfterDelivery = false
itemHandler:UpdateDatabase()

--remove item entries successfully delivered (item transfer bug protection)
local tInventory = unitCourier:GetInventory(false)
local tCourierSlots = shoppingLib.tCourierSlots
local nIndex = 1
while nIndex <= #tCourierSlots do
local tCourierEntry = tCourierSlots[nIndex]
local nSlot = tCourierEntry[1]
local item = nSlot and tInventory[nSlot]
if item then
nIndex = nIndex + 1
else
tremove(shoppingLib.tCourierSlots, nIndex)
end
end
if shoppingLib.bDevelopeItemBuildSaver then SyncWithDatabse() end
shoppingLib.nCourierState = 3 -- Home
shoppingLib.bDelivery = false
end
end
--Delivery Complete
else
--nCourierState = 3 --> Send Courier home
--unit just respawned after failed mission - try to deliver again
if core.unitSelf:IsAlive() and shoppingLib.bDelivery then
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Hero has respawned") end
--resend courier
shoppingLib.nCourierState = 2 --Delivery
shoppingLib.bDelivery = false
end

--Waiting for courier to be usable
if unitCourier:CanAccessStash() then
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Courier can access stash. Ending mission") end

local bSuccess = shoppingLib.FillStash(unitCourier)
if bSuccess then
shoppingLib.nCourierState = 1 --Filling

bOnMission = false
if shoppingLib.bDevelopeItemBuildSaver then SyncWithDatabse() end
end
end
--Home Complete
end
end

return bOnMission
end

--courier repair function
local function CheckCourierBugged(botBrain, courier)
--Courier is a multi user controlled unit, so it may bugged out

--end of courier mission; don't track courier
if not shoppingLib.bCourierMissionControl and core.IsTableEmpty(shoppingLib.tCourierSlots) then
shoppingLib.vecCourierLastPosition = nil
return
end

local nNow = HoN.GetGameTime()
local vecCourierPosition = courier:GetPosition()

--no position set or courier is moving update position and time
if not shoppingLib.vecCourierLastPosition or Vector3.Distance2DSq(vecCourierPosition, shoppingLib.vecCourierLastPosition) > 100 then
shoppingLib.vecCourierLastPosition = vecCourierPosition
shoppingLib.nCourierBuggedTimer = nNow
return
end

--current tracking
--check for bugs
if shoppingLib.nCourierState == 2 then
--want to deliver
if shoppingLib.nCourierBuggedTimer + shoppingLib.nCourierDeliveryTimeOut <= nNow then
--unit is not moving for 1.5s and we want to deliver... request a new delivery order
--deliver
local abilCourierSend = courier:GetAbility(2)
if abilCourierSend then
core.OrderAbility(botBrain, abilCourierSend, nil, true)
shoppingLib.bDelivery = true
end
shoppingLib.nCourierBuggedTimer = nNow
end
else
--otherwise
if shoppingLib.nCourierBuggedTimer + shoppingLib.nCourierPositionTimeOut <= nNow then
--home
local abilCourierHome = courier:GetAbility(3)
if abilCourierHome then
core.OrderAbility(botBrain, abilCourierHome, nil, true)
end
shoppingLib.nCourierBuggedTimer = nNow
end
end

if core.unitSelf:IsAlive() then
shoppingLib.bCourierMissionControl = true
end

end

---------------------------------------------------
-- On think: TeambotBrain and Courier Control
---------------------------------------------------
function shoppingLib:onThinkShopping(tGameVariables)

--old onThink
self:onthinkPreShoppingLib(tGameVariables)

--Courier Control
local nNow = HoN.GetGameTime()
if shoppingLib.nNextCourierControl <= nNow then

shoppingLib.nNextCourierControl = nNow + shoppingLib.nCourierControlIntervall

local unitCourier = shoppingLib.GetCourier()

--no courier? no action
if unitCourier then
if shoppingLib.bCourierMissionControl then
shoppingLib.bCourierMissionControl = CourierMission (self, unitCourier)
end

--repair courier usage (multi control problems)
CheckCourierBugged(self, unitCourier)

--activate shield if needed
local abilCourierShield = unitCourier:GetAbility(1)
local nCourierHealthPercent = unitCourier:GetHealthPercent()
if abilCourierShield and abilCourierShield:CanActivate() and nCourierHealthPercent < 1 then
if shoppingLib.bDebugInfoCourierRelated then BotEcho("Activate Shield") end
core.OrderAbility(self, abilCourierShield)
end
end
end

--Update itemLists
shoppingLib.UpdateItemList()
end
object.onthinkPreShoppingLib = object.onthink
object.onthink = shoppingLib.onThinkShopping


---------------------------------------------------
--Default items
---------------------------------------------------

--[[ list code:
"# Item" is "get # of these"
"Item #" is "get this level of the item" --]]
shoppingLib.tStartingItems = {"2 Item_DuckBoots", "2 Item_MinorTotem", "Item_HealthPotion", "Item_RunesOfTheBlight"}
shoppingLib.tLaneItems = {"Item_Marchers", "2 Item_Soulscream", "Item_EnhancedMarchers"}
shoppingLib.tMidItems = {"Item_Pierce 1", "Item_Immunity", "Item_Pierce 3"} --Pierce is Shieldbreaker, Immunity is Shrunken Head
shoppingLib.tLateItems = {"Item_Weapon3", "Item_Sicarius", "Item_ManaBurn2", "Item_BehemothsHeart", "Item_Damage9" } --Weapon3 is Savage Mace. Item_Sicarius is Firebrand. ManaBurn2 is Geomenter's Bane. Item_Damage9 is Doombringer

---------------------------------------------------
--Unused functions
---------------------------------------------------
--[[
--I don't know, if this function is still usefull or needed anymore
function behaviorLib.ShuffleCombine(botBrain, nextItemDef, unit)
local inventory = unit:GetInventory(true)
if behaviorLib.printShopDebug then
BotEcho("ShuffleCombine for "..nextItemDef:GetName())
end
--locate all my components
local componentDefs = nextItemDef:GetComponents()
local numComponents = #componentDefs
--printGetNameTable(componentDefs)
local slotsToMove = {}
if componentDefs and #componentDefs > 1 then
for slot = 1, 12, 1 do
local curItem = inventory[slot]
if curItem then
--if curItem IS the same type, check if it is our recipe and not another (completed) instance
local bRecipeCheck = curItem:GetTypeID() ~= nextItemDef:GetTypeID() or curItem:IsRecipe()
if behaviorLib.printShopDebug then
BotEcho(" Checking if "..tostring(slot)..", "..curItem:GetName().." is a component")
BotEcho(" NextItem Type check: "..tostring(curItem:GetTypeID()).." ~= "..tostring(nextItemDef:GetTypeID()).." is "..tostring(curItem:GetTypeID() ~= nextItemDef:GetTypeID()))
BotEcho(" IsRecipe chieck: "..tostring(curItem:IsRecipe()))
end
for compSlot, compDef in ipairs(componentDefs) do
if compDef then
if behaviorLib.printShopDebug then
BotEcho(" Component Type check: "..tostring(curItem:GetTypeID()).." == "..tostring(compDef:GetTypeID()).." is "..tostring(curItem:GetTypeID() == compDef:GetTypeID()))
end
if curItem:GetTypeID() == compDef:GetTypeID() and bRecipeCheck then
tinsert(slotsToMove, slot)
tremove(componentDefs, compSlot) --remove this out so we don't mark wrong duplicates
if behaviorLib.printShopDebug then
BotEcho(" Component found!")
end
break
end
end
end
elseif behaviorLib.printShopDebug then
BotEcho(" Checking if "..tostring(slot)..", EMPTY_SLOT is a component")
end
end
if behaviorLib.printShopDebug then
BotEcho("ShuffleCombine - numComponents "..numComponents.." #slotsToMove "..#slotsToMove)
BotEcho("slotsToMove:")
core.printTable(slotsToMove)
end
if numComponents == #slotsToMove then
if behaviorLib.printShopDebug then
BotEcho("Finding Slots to swap into")
end
--swap all components into your stash to combine them, avoiding any components in your stash already
local destSlot = 7
for _, slot in ipairs(slotsToMove) do
if slot < 7 then
--Make sure we don't swap with another component
local num = core.tableContains(slotsToMove, destSlot)
while num > 0 do
destSlot = destSlot + 1
num = core.tableContains(slotsToMove, destSlot)
end
if behaviorLib.printShopDebug then
BotEcho("Swapping: "..slot.." to "..destSlot)
end
unit:SwapItems(slot, destSlot)
destSlot = destSlot + 1
end
end
end
end
end
--]]
59 changes: 59 additions & 0 deletions bots/teambot/teambotbrain.lua
Original file line number Original file line Diff line number Diff line change
@@ -1976,6 +1976,65 @@ function object:GetDefenseTarget(unitAsking)
return nil return nil
end end


object.tItemReservations = {
--AbyssalSkull
Item_LifeSteal5 = false,
--Nomes Wisdom
Item_NomesWisdom = false,
--Sols Bulwark
Item_SolsBulwark = false,
--Daemonic Breastplate
Item_DaemonicBreastplate = false,
--Barrier Idol
Item_BarrierIdol = false,
--Astrolabe
Item_Astrolabe = false,
--Mock of Brilliance
Item_Damage10 = false
}

function object.ReserveItem(itemName)

local debugTeamBotBrain = false

if not itemName then return false end

--check if reserved
local tReservationTable = object.tItemReservations

local bReserved = tReservationTable[itemName]

if debugTeamBotBrain then BotEcho(itemName.." was found in reservation table: "..tostring(bReserved)) end

--if item is not reserved or not tracked, you can buy it
if bReserved ~= false then
return not bReserved
end

--item is not reserved... need further checks
local bFoundItem = false

--check if an instance of the item is in the inventory of not supported heroes (without this lib or human players)
local tAllyHeroes= object.tAllyHeroes
for index, hero in pairs(tAllyHeroes) do
--we can only check the inventory... :(
local inventory = hero:GetInventory (false)
bFoundItem = core.InventoryContains(inventory, itemName, false, false)
if #bFoundItem > 0 then
if debugTeamBotBrain then BotEcho(itemName.." was found in an allies inventory: ") end
bFoundItem = true
break
else
bFoundItem = false
end
end

--Reserve item
tReservationTable[itemName] = true

--if item was not found, we can buy it
return not bFoundItem
end


--[[ colors: --[[ colors:
red red
273 changes: 247 additions & 26 deletions bots/witchslayer/witchslayer_main.lua
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,24 @@
--WitchSlayerBot v1.0 --WitchSlayerBot v1.0
--[[
Tutorial: Advanced ShoppingLib implementation
This S2-Bot implements the advanced shopping system.
The interesting parts are well commented for an easy implementation into your own bot
Tutorial Contents:
-Change standard behavior - Line 75
-Requesting wards - Line 168
-Custom itembuild: Introduction - Line 190
-The Item-Handler - Line 555
Call items by name -> easier search for items.
Take a look at FlintBot for a usage without FindItems-function
There are two types for comments:
1.'--' short explanations
2. '--[.[' and '--].]' for a detailed description (without '.')
Please jump to line 75.
--]]


local _G = getfenv(0) local _G = getfenv(0)
local object = _G.object local object = _G.object
@@ -52,6 +71,221 @@ BotEcho('loading witchslayer_main...')


object.heroName = 'Hero_WitchSlayer' object.heroName = 'Hero_WitchSlayer'



--------------------------------------
-- Advanced ShoppingLib Implementation
--------------------------------------


--[[
Set some easy to remeber references to the shopping and item handler.
shoppingLib: functions to change the shopping behavior
item handler: functions for an easier usage of items
--]]
--Set references to handlers
local itemHandler = object.itemHandler
local shoppingLib = object.shoppingLib


--[[
Because the itemlists and builds can be a dynamic process, you
have to save them, while the bot is in developement.
If 'bDevelopeItemBuildSaver' is off, the bot will lose its decisions
after reloading the bot files midgame.
Turn it off (or delete / comment out, if you are going to submit the bot)
]]--
--Enable ReloadBots compatibility while the bot is in developement
shoppingLib.bDevelopeItemBuildSaver = true


--[[
Overview: Standard shopping behavior:
(alternate options in brackets)
- The bot will reserve team items and will not buy items, team-members already have
Examples of team items: Nomes Wisdom, Mock, Daemonic Breastplate
bReserveItems = true (false)
- The bot will not wait for his lane before shopping
Turn it on, if you alter your item builds depending on your lane (mid, safe...)
bWaitForLaneDecision = false (true)
- The bot will buy Health-Potions, Homecoming Stones and Blight Stones on his own.
You can turn on/off Health- and Mana-Potions, Homecoming- and Blight Stones
or deactivate the automatic purchaise all together
tConsumableOptions = true ( tItems, false)
tItems = {
Item_HomecomingStone = true, (false)
Item_HealthPotion = true, (false)
Item_RunesOfTheBlight = true, (false)
Item_ManaPotion = true (false)
}
-The bot will use the courier, but they will never upgrade or rebuy it.
If you want to make them care, turn this switch on.
bCourierCare = false (true)
If you want to change the behavior options, you only have to pass the changes. (see below)
You can change the behavior setup, whenever you like to do it (transition from support to carry etc.)
Overview SetupOptions structure:
default setup options:
tSetupOptions = {
bReserveItems = true,
bWaitForLaneDecision = false,
tConsumableOptions ={
Item_HomecomingStone = true,
Item_HealthPotion = true,
Item_RunesOfTheBlight = true,
Item_ManaPotion = true
},
bCourierCare = false
}
This bot will support his team, so he should upgrade the courier, buy wards (disabled), but he shouldn't buy any Mana Potions
--]]
--Implement changes to default settings
local tSetupOptions = {
--upgrade courier
bCourierCare = true,
--wait for lane decision before shopping, because we want to change our starting items depending on lane decision
bWaitForLaneDecision = true,
--don't autobuy Mana Potions
tConsumableOptions = {Item_ManaPotion = false}
}
--call setup function
shoppingLib.Setup(tSetupOptions)


--Requesting Wards of Sight
--[[
Because Wards are a cheap item, we have to reserve an item-slot for them.
What is more, we have to call the purchase onto a regular basis. (take a look at the onthink-method line: XXX)
default item slots:
1: Boots of Choice
2: Magic Armor
3: Potal Key
6: Homecoming Stone
Good spots for custom decisions are slot 4, 5 and 3 (if you don't get a pk).
The following function will reserve item slot 4 for Wards of Sight
]]--
--Swap Wards into inventory-slot 4
shoppingLib.SetItemSlotNumber("Item_FlamingEye", 4)

--start buying wards at 2 minutes
object.nextWard = 2*60*1000


----------------------
--Custom Item Build
----------------------

--[[
Overview:
If you want to use custom item build, you have to override the function 'shoppingLib.CheckItemBuild'.
This function is called, whenever the bot runs out of items.
For a dynamic item build you may want to put only one item into the queue at any time (or just a few)
"shoppingLib.tItemDecisions" is an empty table, which can be used to save your custom item decisions.
If "shoppingLib.bDevelopeItemBuildSaver" is turned on, "shoppingLib.tItemDecisions" will also be saved.
Insert the item-codes into 'shopping.Itembuild'.
This bot:
This bot will only change his start-items.
Mid: Blight Stones, 2 Minor Totem, 2 Mark of the Novice and a Health Potion
Not-Mid: Guardian Ring, Ptretenders Crown, Minor Totem, Health Potion and Blight Stones
This function is called 3 times over the bot game:
At start: Decide start items.
After finishing start items: Insert all the other items
After finishing: Will not find any new items and stop item shopping
--]]
--custom item build function
local function WitchSlayerItemBuilder()
--called everytime your bot runs out of items, should return false if you are done with shopping
local debugInfo = false

if debugInfo then BotEcho("Checking itembuilder of Witch Slayer") end

--variable for new items / keep shopping
local bNewItems = false

--get itembuild decision table
local tItemDecisions = shoppingLib.tItemDecisions
if debugInfo then BotEcho("Found ItemDecisions"..type(tItemDecisions)) end

--If tItemDecisions["Lane"] is not set yet, choose lane items
if not tItemDecisions.Lane then

if debugInfo then BotEcho("Choose starting items") end

--check our lane
local tLane = core.tMyLane
if tLane then
--we found our lane, checkout its information

if debugInfo then BotEcho("Found my Lane") end

local tStartingItems = nil

if tLane.sLaneName == "middle" then
--our bot was assigned to the middle lane
if debugInfo then BotEcho("I will take the Mid-Lane.") end
tStartingItems = {"Item_RunesOfTheBlight", "2 Item_MinorTotem", "Item_HealthPotion", "2 Item_MarkOfTheNovice"}
else
--our bot was assigned to a side-lane lane
if debugInfo then BotEcho("Argh, I am not mid *sob*") end
tStartingItems = shoppingLib.tStartingItems
end
--insert decisions into our itembuild-table
core.InsertToTable(shoppingLib.tItembuild, tStartingItems)

--we have implemented new items, so we can keep shopping
bNewItems = true

--remember our decision
tItemDecisions.Lane = true
else
--lane is not set yet, this will cause issues in further item developement
if debugInfo then BotEcho("No Lane set. Bot will skip start items now") end
end
--If tItemDecisions["Rest"] is not set yet, insert all other items into our shopping list
elseif not tItemDecisions.Rest then

if debugInfo then BotEcho("Insert Rest of Items") end

--insert decisions into our itembuild-table
core.InsertToTable(shoppingLib.tItembuild, shoppingLib.tLaneItems)
core.InsertToTable(shoppingLib.tItembuild, shoppingLib.tMidItems)
core.InsertToTable(shoppingLib.tItembuild, shoppingLib.tLateItems)

--we have implemented new items, so we can keep shopping
bNewItems = true

--remember our decision
tItemDecisions.Rest = true
end

if debugInfo then BotEcho("Reached end of itembuilder-function. Keep shopping? "..tostring(bNewItems)) end
return bNewItems
end
object.oldItembuilder = shoppingLib.CheckItemBuild
shoppingLib.CheckItemBuild = WitchSlayerItemBuilder
--please jump to line 555

-------------------------------- --------------------------------
-- Lanes -- Lanes
-------------------------------- --------------------------------
@@ -329,33 +563,20 @@ behaviorLib.HarassHeroBehavior["Execute"] = HarassHeroExecuteOverride
---------------------------------- ----------------------------------
local function funcFindItemsOverride(botBrain) local function funcFindItemsOverride(botBrain)
object.FindItemsOld(botBrain) object.FindItemsOld(botBrain)

core.ValidateItem(core.itemAstrolabe)
core.ValidateItem(core.itemSheepstick)


--only update if we need to --Just call for the item by name (Sheepstick here)
if core.itemSheepstick and core.itemAstrolabe then core.itemSheepstick = itemHandler:GetItem("Item_Morph")
return
end --if you want to add additional information, use the following structure

core.itemAstrolabe = itemHandler:GetItem("Item_Astrolabe")
local inventory = core.unitSelf:GetInventory(false) if core.itemAstrolabe and not core.itemAstrolabe.nHealValue then
for slot = 1, 6, 1 do core.itemAstrolabe.nHealValue = 200
local curItem = inventory[slot] core.itemAstrolabe.nRadius = 600
if curItem then
if core.itemAstrolabe == nil and curItem:GetName() == "Item_Astrolabe" then
core.itemAstrolabe = core.WrapInTable(curItem)
core.itemAstrolabe.nHealValue = 200
core.itemAstrolabe.nRadius = 600
--Echo("Saving astrolabe")
elseif core.itemSheepstick == nil and curItem:GetName() == "Item_Morph" then
core.itemSheepstick = core.WrapInTable(curItem)
end
end
end end
end end
object.FindItemsOld = core.FindItems object.FindItemsOld = core.FindItems
core.FindItems = funcFindItemsOverride core.FindItems = funcFindItemsOverride

--This is the end of ShoppingLib tutorial


---------------------------------- ----------------------------------
-- Witch Slayer's Help behavior -- Witch Slayer's Help behavior
@@ -524,13 +745,13 @@ tinsert(behaviorLib.tBehaviors, behaviorLib.HealBehavior)
--[[ list code: --[[ list code:
"# Item" is "get # of these" "# Item" is "get # of these"
"Item #" is "get this level of the item" --]] "Item #" is "get this level of the item" --]]
behaviorLib.StartingItems = shoppingLib.tStartingItems =
{"Item_GuardianRing", "Item_PretendersCrown", "Item_MinorTotem", "Item_HealthPotion", "Item_RunesOfTheBlight"} {"Item_GuardianRing", "Item_PretendersCrown", "Item_MinorTotem", "Item_HealthPotion", "Item_RunesOfTheBlight"}
behaviorLib.LaneItems = shoppingLib.tLaneItems =
{"Item_ManaRegen3", "Item_Marchers", "Item_Striders", "Item_GraveLocket"} --ManaRegen3 is Ring of the Teacher {"Item_ManaRegen3", "Item_Marchers", "Item_Striders", "Item_GraveLocket"} --ManaRegen3 is Ring of the Teacher
behaviorLib.MidItems = shoppingLib.tMidItems =
{"Item_SacrificialStone", "Item_NomesWisdom", "Item_Astrolabe", "Item_Intelligence7"} --Intelligence7 is Staff of the Master {"Item_SacrificialStone", "Item_NomesWisdom", "Item_Astrolabe", "Item_Intelligence7"} --Intelligence7 is Staff of the Master
behaviorLib.LateItems = shoppingLib.tLateItems =
{"Item_Morph", "Item_BehemothsHeart", 'Item_Damage9'} --Morph is Sheepstick. Item_Damage9 is Doombringer {"Item_Morph", "Item_BehemothsHeart", 'Item_Damage9'} --Morph is Sheepstick. Item_Damage9 is Doombringer




loaded diff for bots/behaviorLib.lua